Commit dff51569 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Add external extensions loader for pre-installed demo session apps

External loader that loads extension CRXs from the offline demo
resources image mounted by the image loader service.

It expects that offline demo resources, in addition to actual CRX files
to install, a JSON that maps bundled app IDs to the CRX location and
app version (in the same format as for other external extensions, e.g.
default Chrome OS apps).

Bug: 863162

Change-Id: I88aa544be10d74c1ead10a685bb7fd238f18cdd0
Reviewed-on: https://chromium-review.googlesource.com/1134579Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Reviewed-by: default avatarMichael Giuffrida <michaelpg@chromium.org>
Commit-Queue: Toni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577648}
parent 121aee7c
......@@ -917,6 +917,8 @@ source_set("chromeos") {
"login/chrome_restart_request.h",
"login/demo_mode/demo_app_launcher.cc",
"login/demo_mode/demo_app_launcher.h",
"login/demo_mode/demo_extensions_external_loader.cc",
"login/demo_mode/demo_extensions_external_loader.h",
"login/demo_mode/demo_mode_detector.cc",
"login/demo_mode/demo_mode_detector.h",
"login/demo_mode/demo_session.cc",
......@@ -2061,6 +2063,7 @@ source_set("unit_tests") {
"lock_screen_apps/lock_screen_profile_creator_impl_unittest.cc",
"lock_screen_apps/state_controller_unittest.cc",
"login/auth/cryptohome_authenticator_unittest.cc",
"login/demo_mode/demo_extensions_external_loader_unittest.cc",
"login/demo_mode/demo_session_unittest.cc",
"login/demo_mode/demo_setup_controller_unittest.cc",
"login/demo_mode/demo_setup_test_utils.h",
......
......@@ -822,6 +822,13 @@ void ChromeBrowserMainPartsChromeos::PreProfileInit() {
parsed_command_line().GetSwitchValueASCII(switches::kLoginProfile);
session_manager::SessionManager::Get()->CreateSessionForRestart(
account_id, user_id_hash);
// If restarting demo session, mark demo session as started before primary
// profile starts initialization so browser context keyed services created
// with the browser context (for example ExtensionService) can use
// DemoSession::started().
DemoSession::StartIfInDemoMode();
VLOG(1) << "Relaunching browser for user: " << account_id.Serialize()
<< " with hash: " << user_id_hash;
}
......
// Copyright 2018 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 "chrome/browser/chromeos/login/demo_mode/demo_extensions_external_loader.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/optional.h"
#include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_traits.h"
#include "base/values.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/external_provider_impl.h"
namespace chromeos {
namespace {
// Arbitrary, but reasonable size limit in bytes for prefs file.
constexpr size_t kPrefsSizeLimit = 1024 * 1024;
base::Optional<base::Value> LoadPrefsFromDisk(
const base::FilePath& prefs_path) {
if (!base::PathExists(prefs_path)) {
LOG(WARNING) << "Demo extensions prefs not found " << prefs_path.value();
return base::nullopt;
}
std::string prefs_str;
if (!base::ReadFileToStringWithMaxSize(prefs_path, &prefs_str,
kPrefsSizeLimit)) {
LOG(ERROR) << "Failed to read prefs " << prefs_path.value() << "; "
<< "failed after reading " << prefs_str.size() << " bytes";
return base::nullopt;
}
std::unique_ptr<base::Value> prefs_value = base::JSONReader::Read(prefs_str);
if (!prefs_value) {
LOG(ERROR) << "Unable to parse demo extensions prefs.";
return base::nullopt;
}
if (!prefs_value->is_dict()) {
LOG(ERROR) << "Demo extensions prefs not a dictionary.";
return base::nullopt;
}
return base::Value::FromUniquePtrValue(std::move(prefs_value));
}
} // namespace
// static
bool DemoExtensionsExternalLoader::SupportedForProfile(Profile* profile) {
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile))
return false;
DemoSession* demo_session = DemoSession::Get();
return demo_session && demo_session->started();
}
DemoExtensionsExternalLoader::DemoExtensionsExternalLoader()
: weak_ptr_factory_(this) {
DCHECK(DemoSession::Get() && DemoSession::Get()->started());
}
DemoExtensionsExternalLoader::~DemoExtensionsExternalLoader() = default;
void DemoExtensionsExternalLoader::StartLoading() {
DemoSession::Get()->EnsureOfflineResourcesLoaded(base::BindOnce(
&DemoExtensionsExternalLoader::StartLoadingFromOfflineDemoResources,
weak_ptr_factory_.GetWeakPtr()));
}
void DemoExtensionsExternalLoader::StartLoadingFromOfflineDemoResources() {
DemoSession* demo_session = DemoSession::Get();
DCHECK(demo_session->offline_resources_loaded());
base::FilePath demo_extension_list =
demo_session->GetExternalExtensionsPrefsPath();
if (demo_extension_list.empty()) {
LoadFinished(std::make_unique<base::DictionaryValue>());
return;
}
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&LoadPrefsFromDisk, demo_extension_list),
base::BindOnce(
&DemoExtensionsExternalLoader::DemoExternalExtensionsPrefsLoaded,
weak_ptr_factory_.GetWeakPtr()));
}
void DemoExtensionsExternalLoader::DemoExternalExtensionsPrefsLoaded(
base::Optional<base::Value> prefs) {
if (!prefs.has_value()) {
LoadFinished(std::make_unique<base::DictionaryValue>());
return;
}
DCHECK(prefs.value().is_dict());
DemoSession* demo_session = DemoSession::Get();
DCHECK(demo_session);
// Adjust CRX paths in the prefs. Prefs on disk contains paths relative to
// the offline demo resources root - they have to be changed to absolute paths
// so extensions service knows from where to load them.
for (auto&& dict_item : prefs.value().DictItems()) {
if (!dict_item.second.is_dict())
continue;
const base::Value* path = dict_item.second.FindKeyOfType(
extensions::ExternalProviderImpl::kExternalCrx,
base::Value::Type::STRING);
if (!path || !path->is_string())
continue;
base::FilePath relative_path = base::FilePath(path->GetString());
if (relative_path.IsAbsolute()) {
LOG(ERROR) << "Ignoring demo extension with an absolute path "
<< dict_item.first;
dict_item.second.RemoveKey(
extensions::ExternalProviderImpl::kExternalCrx);
continue;
}
dict_item.second.SetKey(
extensions::ExternalProviderImpl::kExternalCrx,
base::Value(demo_session->GetOfflineResourceAbsolutePath(relative_path)
.value()));
}
LoadFinished(base::DictionaryValue::From(
base::Value::ToUniquePtrValue(std::move(prefs.value()))));
}
} // namespace chromeos
// Copyright 2018 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.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_DEMO_MODE_DEMO_EXTENSIONS_EXTERNAL_LOADER_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_DEMO_MODE_DEMO_EXTENSIONS_EXTERNAL_LOADER_H_
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chrome/browser/extensions/external_loader.h"
class Profile;
namespace base {
class Value;
}
namespace chromeos {
// External loader for extensions to be loaded into demo mode sessions. The CRX
// files are loaded from preinstalled demo mode resources image mounted by
// image loader service (from the device's stateful partition).
// The loader reads external extensions prefs from the mounted demo resource
// image, and converts crx paths, which are expected to be relative to the
// mounted demo resources root to absolute paths that can be used by external
// extensions provider.
// NOTE: The class is expected to be used on the UI thread exclusively.
class DemoExtensionsExternalLoader : public extensions::ExternalLoader {
public:
// Whether demo apps should be loaded for the profile - i.e. whether the
// profile is the primary profile in a demo session.
static bool SupportedForProfile(Profile* profile);
DemoExtensionsExternalLoader();
// extensions::ExternalLoader:
void StartLoading() override;
protected:
~DemoExtensionsExternalLoader() override;
private:
// Starts loading the external extensions prefs. Passed as callback to
// DemoSession::EnsureOfflineResourcesLoaded() in StartLoading() - it
// will get called when offline demo resources have finished loading.
void StartLoadingFromOfflineDemoResources();
// Called when the external extensions prefs are read from the disk.
// |prefs| - demo extensions prefs.
void DemoExternalExtensionsPrefsLoaded(base::Optional<base::Value> prefs);
base::WeakPtrFactory<DemoExtensionsExternalLoader> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(DemoExtensionsExternalLoader);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_DEMO_MODE_DEMO_EXTENSIONS_EXTERNAL_LOADER_H_
......@@ -37,6 +37,9 @@ constexpr base::FilePath::CharType kOfflineResourcesComponentPath[] =
constexpr base::FilePath::CharType kDemoAppsPath[] =
FILE_PATH_LITERAL("android_demo_apps.squash");
constexpr base::FilePath::CharType kExternalExtensionsPrefsPath[] =
FILE_PATH_LITERAL("demo_extensions.json");
bool IsDemoModeOfflineEnrolled() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(DemoSession::IsDeviceInDemoMode());
......@@ -131,6 +134,21 @@ base::FilePath DemoSession::GetDemoAppsPath() const {
return offline_resources_path_.Append(kDemoAppsPath);
}
base::FilePath DemoSession::GetExternalExtensionsPrefsPath() const {
if (offline_resources_path_.empty())
return base::FilePath();
return offline_resources_path_.Append(kExternalExtensionsPrefsPath);
}
base::FilePath DemoSession::GetOfflineResourceAbsolutePath(
const base::FilePath& relative_path) const {
if (offline_resources_path_.empty())
return base::FilePath();
if (relative_path.ReferencesParent())
return base::FilePath();
return offline_resources_path_.Append(relative_path);
}
DemoSession::DemoSession()
: offline_enrolled_(IsDemoModeOfflineEnrolled()), weak_ptr_factory_(this) {}
......
......@@ -62,6 +62,18 @@ class DemoSession {
// will be set when the offline resources get loaded.
base::FilePath GetDemoAppsPath() const;
// Gets the path under offline demo resources mount point that contains
// external extensions prefs (JSON containing set of extensions to be loaded
// as external extensions into demo sessions - expected to map extension IDs
// to the associated CRX path and version).
base::FilePath GetExternalExtensionsPrefsPath() const;
// Converts a relative path to an absolute path under the offline demo
// resources mount. Returns an empty string if the offline demo resources are
// not loaded.
base::FilePath GetOfflineResourceAbsolutePath(
const base::FilePath& relative_path) const;
bool offline_enrolled() const { return offline_enrolled_; }
bool started() const { return started_; }
......
......@@ -26,6 +26,7 @@ constexpr char kOfflineResourcesComponent[] = "demo_mode_resources";
constexpr char kTestDemoModeResourcesMountPoint[] =
"/run/imageloader/demo_mode_resources";
constexpr char kDemoAppsImageFile[] = "android_demo_apps.squash";
constexpr char kExternalExtensionsPrefsFile[] = "demo_extensions.json";
void SetBoolean(bool* value) {
*value = true;
......@@ -163,6 +164,23 @@ TEST_F(DemoSessionTest, StartInitiatesOfflineResourcesLoad) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
EXPECT_EQ(
component_mount_point.AppendASCII("foo.txt"),
demo_session->GetOfflineResourceAbsolutePath(base::FilePath("foo.txt")));
EXPECT_EQ(component_mount_point.AppendASCII("foo/bar.txt"),
demo_session->GetOfflineResourceAbsolutePath(
base::FilePath("foo/bar.txt")));
EXPECT_EQ(
component_mount_point.AppendASCII("foo/"),
demo_session->GetOfflineResourceAbsolutePath(base::FilePath("foo/")));
EXPECT_TRUE(
demo_session->GetOfflineResourceAbsolutePath(base::FilePath("../foo/"))
.empty());
EXPECT_TRUE(
demo_session->GetOfflineResourceAbsolutePath(base::FilePath("foo/../bar"))
.empty());
}
TEST_F(DemoSessionTest, StartForDemoDeviceNotInDemoMode) {
......@@ -212,6 +230,8 @@ TEST_F(DemoSessionTest, PreloadOfflineResourcesIfInDemoMode) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
}
TEST_F(DemoSessionTest, PreloadOfflineResourcesIfNotInDemoMode) {
......@@ -271,6 +291,8 @@ TEST_F(DemoSessionTest, StartDemoSessionWhilePreloadingResources) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
}
TEST_F(DemoSessionTest, StartDemoSessionAfterPreloadingResources) {
......@@ -289,6 +311,8 @@ TEST_F(DemoSessionTest, StartDemoSessionAfterPreloadingResources) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
EXPECT_EQ(std::list<std::string>(), image_loader_client_->pending_loads());
}
......@@ -316,6 +340,8 @@ TEST_F(DemoSessionTest, EnsureOfflineResourcesLoadedAfterStart) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
}
TEST_F(DemoSessionTest, EnsureOfflineResourcesLoadedAfterOfflineResourceLoad) {
......@@ -338,6 +364,8 @@ TEST_F(DemoSessionTest, EnsureOfflineResourcesLoadedAfterOfflineResourceLoad) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
}
TEST_F(DemoSessionTest, EnsureOfflineResourcesLoadedAfterPreload) {
......@@ -365,6 +393,8 @@ TEST_F(DemoSessionTest, EnsureOfflineResourcesLoadedAfterPreload) {
EXPECT_TRUE(demo_session->offline_resources_loaded());
EXPECT_EQ(component_mount_point.AppendASCII(kDemoAppsImageFile),
demo_session->GetDemoAppsPath());
EXPECT_EQ(component_mount_point.AppendASCII(kExternalExtensionsPrefsFile),
demo_session->GetExternalExtensionsPrefsPath());
}
TEST_F(DemoSessionTest, MultipleEnsureOfflineResourcesLoaded) {
......
......@@ -112,8 +112,7 @@ void StartUserSession(Profile* user_profile, const std::string& login_user_id) {
return;
}
chromeos::DemoSession* demo_session =
chromeos::DemoSession::StartIfInDemoMode();
chromeos::DemoSession* demo_session = chromeos::DemoSession::Get();
// In demo session, delay starting user session until the offline demo
// session resources have been loaded.
if (demo_session && demo_session->started() &&
......
......@@ -51,6 +51,7 @@
#include "chrome/browser/chromeos/app_mode/kiosk_app_external_loader.h"
#include "chrome/browser/chromeos/customization/customization_document.h"
#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_extensions_external_loader.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/device_local_account.h"
#include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
......@@ -689,6 +690,20 @@ void ExternalProviderImpl::CreateExternalProviders(
Manifest::EXTERNAL_PREF, Manifest::EXTERNAL_PREF_DOWNLOAD,
oem_extension_creation_flags));
}
// For Chrome OS demo sessions, add pre-installed demo extensions and apps.
if (chromeos::DemoExtensionsExternalLoader::SupportedForProfile(profile)) {
std::unique_ptr<ExternalProviderImpl> demo_apps_provider =
std::make_unique<ExternalProviderImpl>(
service,
base::MakeRefCounted<chromeos::DemoExtensionsExternalLoader>(),
profile, Manifest::EXTERNAL_PREF, Manifest::INVALID_LOCATION,
Extension::NO_FLAGS);
demo_apps_provider->set_auto_acknowledge(true);
demo_apps_provider->set_install_immediately(true);
provider_list->push_back(std::move(demo_apps_provider));
}
#elif defined(OS_LINUX)
if (!profile->IsLegacySupervised()) {
provider_list->push_back(std::make_unique<ExternalProviderImpl>(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment