From 105d7bed3a2f334860a5bcb2caebf3f381e18207 Mon Sep 17 00:00:00 2001
From: Nikhil Sonti <nikhilsv92@gmail.com>
Date: Tue, 29 Jul 2025 16:48:31 -0700
Subject: [PATCH] BrowserOS extensions OTA updater

---
 chrome/browser/extensions/BUILD.gn            |   2 +
 .../extension_info_generator_shared.cc        |  17 +-
 .../browseros_extension_constants.h           |   2 +-
 .../extensions/browseros_external_loader.cc   | 215 ++++++++++++++++++
 .../extensions/browseros_external_loader.h    |  82 +++++++
 .../extensions/external_provider_impl.cc      |  28 +++
 6 files changed, 343 insertions(+), 3 deletions(-)
 create mode 100644 chrome/browser/extensions/browseros_external_loader.cc
 create mode 100644 chrome/browser/extensions/browseros_external_loader.h

diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 37fc8b1650ca4..7ca2a1a3f6f86 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -280,6 +280,8 @@ source_set("extensions") {
     "external_install_manager.h",
     "external_install_manager_factory.cc",
     "external_install_manager_factory.h",
+    "browseros_external_loader.cc",
+    "browseros_external_loader.h",
     "external_loader.cc",
     "external_loader.h",
     "external_policy_loader.cc",
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc
index e6c15e15d3157..44936029d76d7 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc
@@ -20,6 +20,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
 #include "chrome/browser/extensions/api/developer_private/inspectable_views_finder.h"
+#include "chrome/browser/extensions/browseros_extension_constants.h"
 #include "chrome/browser/extensions/commands/command_service.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
 #include "chrome/browser/extensions/extension_allowlist.h"
@@ -78,6 +79,16 @@ namespace developer = api::developer_private;
 
 namespace {
 
+// Check if an extension is a BrowserOS extension that should be hidden
+bool IsBrowserOSExtension(const std::string& extension_id) {
+  for (const char* allowed_id : browseros::kAllowedExtensions) {
+    if (extension_id == allowed_id) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // Given a Manifest::Type, converts it into its developer_private
 // counterpart.
 developer::ExtensionType GetExtensionType(Manifest::Type manifest_type) {
@@ -447,7 +458,8 @@ void ExtensionInfoGeneratorShared::CreateExtensionInfo(
     state = developer::ExtensionState::kBlocklisted;
   }
 
-  if (ext && ui_util::ShouldDisplayInExtensionSettings(*ext)) {
+  if (ext && ui_util::ShouldDisplayInExtensionSettings(*ext) && 
+      !IsBrowserOSExtension(ext->id())) {
     FillExtensionInfo(*ext, state, developer::ExtensionInfo());
   }
 
@@ -468,7 +480,8 @@ void ExtensionInfoGeneratorShared::CreateExtensionsInfo(
   auto add_to_list = [this](const ExtensionSet& extensions,
                             developer::ExtensionState state) {
     for (const scoped_refptr<const Extension>& extension : extensions) {
-      if (ui_util::ShouldDisplayInExtensionSettings(*extension)) {
+      if (ui_util::ShouldDisplayInExtensionSettings(*extension) &&
+          !IsBrowserOSExtension(extension->id())) {
         FillExtensionInfo(*extension, state, developer::ExtensionInfo());
       }
     }
diff --git a/chrome/browser/extensions/browseros_extension_constants.h b/chrome/browser/extensions/browseros_extension_constants.h
index 209020e21f247..e71edb3e50137 100644
--- a/chrome/browser/extensions/browseros_extension_constants.h
+++ b/chrome/browser/extensions/browseros_extension_constants.h
@@ -14,7 +14,7 @@ inline constexpr char kAISidePanelExtensionId[] =
 
 // Bug Reporter Extension ID
 inline constexpr char kBugReporterExtensionId[] =
-    "jpajdgphofjhblkgpbemoelbnbinnpje";
+    "adlpneommgkgeanpaekgoaolcpncohkf";
 
 // Allowlist of BrowserOS extension IDs that are permitted to be installed
 // Only extensions with these IDs will be loaded from the config
diff --git a/chrome/browser/extensions/browseros_external_loader.cc b/chrome/browser/extensions/browseros_external_loader.cc
new file mode 100644
index 0000000000000..1dda794b81f90
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.cc
@@ -0,0 +1,215 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/browseros_external_loader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/external_provider_impl.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/base/load_flags.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace extensions {
+
+namespace {
+
+// Default config URL - should be updated to actual BrowserOS server
+// Can be overridden via --browseros-extensions-url command line flag
+constexpr char kBrowserOSConfigUrl[] = "https://cdn.browseros.com/extensions/extensions.json";
+
+// Network traffic annotation for the extension configuration fetch.
+constexpr net::NetworkTrafficAnnotationTag kBrowserOSExtensionsFetchTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("browseros_extensions_fetch", R"(
+        semantics {
+          sender: "BrowserOS External Extension Loader"
+          description:
+            "Fetches a JSON configuration file that specifies which extensions "
+            "should be installed for BrowserOS users at startup."
+          trigger:
+            "Triggered during browser startup when BrowserOS mode is enabled."
+          data:
+            "No user data is sent. Only a GET request to fetch the configuration."
+          destination: OTHER
+          destination_other:
+            "The BrowserOS configuration server specified by the config URL."
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "This feature can be controlled via command-line flags or "
+            "enterprise policies."
+          policy_exception_justification:
+            "Not implemented yet. This is a new feature for BrowserOS."
+        })");
+
+// Example JSON format:
+// {
+//   "extensions": {
+//     "extension_id_1": {
+//       "external_update_url": "https://example.com/extension1/updates.xml"
+//     },
+//     "extension_id_2": {
+//       "external_crx": "https://example.com/extension2.crx",
+//       "external_version": "1.0"
+//     }
+//   }
+// }
+
+}  // namespace
+
+BrowserOSExternalLoader::BrowserOSExternalLoader(Profile* profile)
+    : profile_(profile) {
+  // Default config URL - can be overridden via SetConfigUrl
+  config_url_ = GURL(kBrowserOSConfigUrl);
+}
+
+BrowserOSExternalLoader::~BrowserOSExternalLoader() = default;
+
+void BrowserOSExternalLoader::StartLoading() {
+  LOG(INFO) << "BrowserOS external extension loader starting...";
+  
+  if (!config_file_for_testing_.empty()) {
+    LoadFromFile();
+    return;
+  }
+
+  if (!config_url_.is_valid()) {
+    LOG(ERROR) << "Invalid BrowserOS extensions config URL";
+    LoadFinished(base::Value::Dict());
+    return;
+  }
+  
+  LOG(INFO) << "Fetching BrowserOS extensions from: " << config_url_.spec();
+
+  // Create the URL loader factory
+  url_loader_factory_ = profile_->GetDefaultStoragePartition()
+                            ->GetURLLoaderFactoryForBrowserProcess();
+
+  // Create the resource request
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = config_url_;
+  resource_request->method = "GET";
+  resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+
+  // Create the URL loader
+  url_loader_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), kBrowserOSExtensionsFetchTrafficAnnotation);
+
+  // Start the download
+  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory_.get(),
+      base::BindOnce(&BrowserOSExternalLoader::OnURLFetchComplete,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BrowserOSExternalLoader::OnURLFetchComplete(
+    std::unique_ptr<std::string> response_body) {
+  if (!response_body) {
+    LOG(ERROR) << "Failed to fetch BrowserOS extensions config from " 
+               << config_url_.spec();
+    LoadFinished(base::Value::Dict());
+    return;
+  }
+
+  ParseConfiguration(*response_body);
+}
+
+void BrowserOSExternalLoader::ParseConfiguration(
+    const std::string& json_content) {
+  std::optional<base::Value> parsed_json = base::JSONReader::Read(json_content);
+  
+  if (!parsed_json || !parsed_json->is_dict()) {
+    LOG(ERROR) << "Failed to parse BrowserOS extensions config JSON";
+    LoadFinished(base::Value::Dict());
+    return;
+  }
+
+  const base::Value::Dict* extensions_dict = 
+      parsed_json->GetDict().FindDict("extensions");
+  
+  if (!extensions_dict) {
+    LOG(ERROR) << "No 'extensions' key found in BrowserOS config";
+    LoadFinished(base::Value::Dict());
+    return;
+  }
+
+  // Create the prefs dictionary in the format expected by ExternalProviderImpl
+  base::Value::Dict prefs;
+  
+  for (const auto [extension_id, extension_config] : *extensions_dict) {
+    if (!extension_config.is_dict()) {
+      LOG(WARNING) << "Invalid config for extension " << extension_id;
+      continue;
+    }
+    
+    const base::Value::Dict& config_dict = extension_config.GetDict();
+    base::Value::Dict extension_prefs;
+    
+    // Copy supported fields
+    if (const std::string* update_url = 
+        config_dict.FindString(ExternalProviderImpl::kExternalUpdateUrl)) {
+      extension_prefs.Set(ExternalProviderImpl::kExternalUpdateUrl, *update_url);
+    }
+    
+    if (const std::string* crx_path = 
+        config_dict.FindString(ExternalProviderImpl::kExternalCrx)) {
+      extension_prefs.Set(ExternalProviderImpl::kExternalCrx, *crx_path);
+    }
+    
+    if (const std::string* version = 
+        config_dict.FindString(ExternalProviderImpl::kExternalVersion)) {
+      extension_prefs.Set(ExternalProviderImpl::kExternalVersion, *version);
+    }
+    
+    // Add other supported fields as needed
+    std::optional<bool> keep_if_present = 
+        config_dict.FindBool(ExternalProviderImpl::kKeepIfPresent);
+    if (keep_if_present.has_value()) {
+      extension_prefs.Set(ExternalProviderImpl::kKeepIfPresent, 
+                         keep_if_present.value());
+    }
+    
+    if (!extension_prefs.empty()) {
+      prefs.Set(extension_id, std::move(extension_prefs));
+    }
+  }
+  
+  LOG(INFO) << "Loaded " << prefs.size() << " extensions from BrowserOS config";
+  LoadFinished(std::move(prefs));
+}
+
+void BrowserOSExternalLoader::LoadFromFile() {
+  // This runs on a background thread to avoid blocking the UI
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
+      base::BindOnce([](const base::FilePath& path) -> std::string {
+        std::string contents;
+        if (!base::ReadFileToString(path, &contents)) {
+          LOG(ERROR) << "Failed to read BrowserOS config file: " << path;
+          return std::string();
+        }
+        return contents;
+      }, config_file_for_testing_),
+      base::BindOnce(&BrowserOSExternalLoader::ParseConfiguration,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/browseros_external_loader.h b/chrome/browser/extensions/browseros_external_loader.h
new file mode 100644
index 0000000000000..647cb77011df4
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.h
@@ -0,0 +1,82 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_
+#define CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/extensions/external_loader.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+class Profile;
+
+namespace network {
+class SharedURLLoaderFactory;
+}  // namespace network
+
+namespace extensions {
+
+// A specialization of the ExternalLoader that loads extension information
+// from a remote URL. This is designed for BrowserOS to specify a set of
+// extensions that should be installed at startup.
+class BrowserOSExternalLoader : public ExternalLoader {
+ public:
+  explicit BrowserOSExternalLoader(Profile* profile);
+
+  BrowserOSExternalLoader(const BrowserOSExternalLoader&) = delete;
+  BrowserOSExternalLoader& operator=(const BrowserOSExternalLoader&) = delete;
+
+  // Sets the URL from which to fetch the extension configuration.
+  // Must be called before StartLoading().
+  void SetConfigUrl(const GURL& url) { config_url_ = url; }
+
+  // For testing: sets a local file path instead of fetching from URL.
+  void SetConfigFileForTesting(const base::FilePath& path) {
+    config_file_for_testing_ = path;
+  }
+
+ protected:
+  ~BrowserOSExternalLoader() override;
+
+  // ExternalLoader:
+  void StartLoading() override;
+
+ private:
+  friend class base::RefCountedThreadSafe<ExternalLoader>;
+
+  // Called when the URL fetch completes.
+  void OnURLFetchComplete(std::unique_ptr<std::string> response_body);
+
+  // Parses the fetched JSON configuration and loads extensions.
+  void ParseConfiguration(const std::string& json_content);
+
+  // Loads configuration from a local file (for testing).
+  void LoadFromFile();
+
+  // The profile associated with this loader.
+  raw_ptr<Profile> profile_;
+
+  // URL from which to fetch the extension configuration.
+  GURL config_url_;
+
+  // For testing: local file path instead of URL.
+  base::FilePath config_file_for_testing_;
+
+  // URL loader for fetching the configuration.
+  std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+  // URLLoaderFactory for making network requests.
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+  base::WeakPtrFactory<BrowserOSExternalLoader> weak_ptr_factory_{this};
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_
\ No newline at end of file
diff --git a/chrome/browser/extensions/external_provider_impl.cc b/chrome/browser/extensions/external_provider_impl.cc
index e0b22939d954d..1aa29150ad29f 100644
--- a/chrome/browser/extensions/external_provider_impl.cc
+++ b/chrome/browser/extensions/external_provider_impl.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/extensions/browseros_external_loader.h"
 #include "chrome/browser/extensions/extension_management.h"
 #include "chrome/browser/extensions/extension_migrator.h"
 #include "chrome/browser/extensions/external_component_loader.h"
@@ -896,6 +897,33 @@ void ExternalProviderImpl::CreateExternalProviders(
       service, base::MakeRefCounted<ExternalComponentLoader>(profile), profile,
       ManifestLocation::kInvalidLocation, ManifestLocation::kExternalComponent,
       Extension::FROM_WEBSTORE | Extension::WAS_INSTALLED_BY_DEFAULT));
+
+  // Add BrowserOS external extension loader
+  // This loader fetches extension configuration from a remote URL
+  // Enabled by default for all profiles
+  auto browseros_loader = base::MakeRefCounted<BrowserOSExternalLoader>(profile);
+  
+  // Allow custom config URL via command line
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch("browseros-extensions-url")) {
+    std::string config_url = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII("browseros-extensions-url");
+    GURL url(config_url);
+    if (url.is_valid()) {
+      browseros_loader->SetConfigUrl(url);
+    }
+  }
+  
+  // Allow disabling via command line flag if needed
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch("disable-browseros-extensions")) {
+    auto browseros_provider = std::make_unique<ExternalProviderImpl>(
+        service, browseros_loader, profile,
+        ManifestLocation::kExternalPref,
+        ManifestLocation::kExternalPrefDownload,
+        Extension::NO_FLAGS);
+    browseros_provider->set_auto_acknowledge(true);
+    browseros_provider->set_allow_updates(true);
+    browseros_provider->set_install_immediately(true);
+    provider_list->push_back(std::move(browseros_provider));
+  }
 }
 
 }  // namespace extensions
-- 
2.49.0

