| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/pending_extension_manager.h" |
| |
| #include <algorithm> |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/version.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/pending_extension_manager_factory.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // Install predicate used by AddFromExternalUpdateUrl(). |
| bool AlwaysInstall(const extensions::Extension* extension, |
| content::BrowserContext* context) { |
| return true; |
| } |
| |
| std::string GetVersionString(const base::Version& version) { |
| return version.IsValid() ? version.GetString() : "invalid"; |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| using content::BrowserThread; |
| using extensions::mojom::ManifestLocation; |
| |
| PendingExtensionManager::PendingExtensionManager( |
| content::BrowserContext* context) |
| : context_(context) {} |
| |
| PendingExtensionManager::~PendingExtensionManager() = default; |
| |
| // static |
| PendingExtensionManager* PendingExtensionManager::Get( |
| content::BrowserContext* browser_context) { |
| return PendingExtensionManagerFactory::GetForBrowserContext(browser_context); |
| } |
| |
| const PendingExtensionInfo* PendingExtensionManager::GetById( |
| const std::string& id) const { |
| auto it = pending_extensions_.find(id); |
| if (it != pending_extensions_.end()) { |
| return &it->second; |
| } |
| |
| return nullptr; |
| } |
| |
| bool PendingExtensionManager::Remove(const std::string& id) { |
| const bool removed = pending_extensions_.erase(id) > 0; |
| if (removed) { |
| for (Observer& observer : observers_) { |
| observer.OnExtensionRemoved(id); |
| } |
| } |
| return removed; |
| } |
| |
| bool PendingExtensionManager::IsIdPending(const std::string& id) const { |
| return GetById(id) != nullptr; |
| } |
| |
| bool PendingExtensionManager::HasPendingExtensions() const { |
| return !pending_extensions_.empty(); |
| } |
| |
| bool PendingExtensionManager::HasPendingExtensionFromSync() const { |
| return std::ranges::any_of( |
| pending_extensions_, |
| [](const std::pair<const std::string, PendingExtensionInfo>& it) { |
| return it.second.is_from_sync(); |
| }); |
| } |
| |
| bool PendingExtensionManager::HasHighPriorityPendingExtension() const { |
| return std::ranges::any_of( |
| pending_extensions_, |
| [](const std::pair<const std::string, PendingExtensionInfo>& it) { |
| return it.second.install_source() == |
| ManifestLocation::kExternalPolicyDownload || |
| it.second.install_source() == |
| ManifestLocation::kExternalComponent; |
| }); |
| } |
| |
| bool PendingExtensionManager::AddFromSync( |
| const std::string& id, |
| const GURL& update_url, |
| const base::Version& version, |
| PendingExtensionInfo::ShouldAllowInstallPredicate should_allow_install, |
| bool remote_install) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (ExtensionRegistry::Get(context_)->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING)) { |
| LOG(ERROR) << "Trying to add pending extension " << id |
| << " which already exists"; |
| return false; |
| } |
| |
| // Make sure we don't ever try to install the CWS app, because even though |
| // it is listed as a syncable app (because its values need to be synced) it |
| // should already be installed on every instance. |
| if (id == extensions::kWebStoreAppId) { |
| return false; |
| } |
| |
| static const bool kIsFromSync = true; |
| static const mojom::ManifestLocation kSyncLocation = |
| mojom::ManifestLocation::kInternal; |
| static const bool kMarkAcknowledged = false; |
| |
| return AddExtensionImpl( |
| id, std::string(), update_url, version, should_allow_install, kIsFromSync, |
| kSyncLocation, Extension::NO_FLAGS, kMarkAcknowledged, remote_install); |
| } |
| |
| bool PendingExtensionManager::AddFromExtensionImport( |
| const std::string& id, |
| const GURL& update_url, |
| PendingExtensionInfo::ShouldAllowInstallPredicate should_allow_install) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (ExtensionRegistry::Get(context_)->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING)) { |
| LOG(ERROR) << "Trying to add pending extension " << id |
| << " which already exists"; |
| return false; |
| } |
| |
| static const bool kIsFromSync = false; |
| static const mojom::ManifestLocation kManifestLocation = |
| mojom::ManifestLocation::kInternal; |
| static const bool kMarkAcknowledged = false; |
| static const bool kRemoteInstall = false; |
| |
| return AddExtensionImpl(id, std::string(), update_url, base::Version(), |
| should_allow_install, kIsFromSync, kManifestLocation, |
| Extension::NO_FLAGS, kMarkAcknowledged, |
| kRemoteInstall); |
| } |
| |
| bool PendingExtensionManager::AddFromExternalUpdateUrl( |
| const std::string& id, |
| const std::string& install_parameter, |
| const GURL& update_url, |
| ManifestLocation location, |
| int creation_flags, |
| bool mark_acknowledged) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| static const bool kIsFromSync = false; |
| static const bool kRemoteInstall = false; |
| |
| const Extension* extension = |
| ExtensionRegistry::Get(context_)->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING); |
| if (extension && location == Manifest::GetHigherPriorityLocation( |
| location, extension->location())) { |
| // If the new location has higher priority than the location of an existing |
| // extension, let the update process overwrite the existing extension. |
| } else { |
| // Skip the installation if the extension was removed by the user and it's |
| // not specified to be force-installed through the policy. |
| if (!Manifest::IsPolicyLocation(location) && |
| ExtensionPrefs::Get(context_)->IsExternalExtensionUninstalled(id)) { |
| return false; |
| } |
| |
| if (extension) { |
| LOG(DFATAL) << "Trying to add extension " << id |
| << " by external update, but it is already installed."; |
| return false; |
| } |
| } |
| |
| return AddExtensionImpl(id, install_parameter, update_url, base::Version(), |
| &AlwaysInstall, kIsFromSync, location, creation_flags, |
| mark_acknowledged, kRemoteInstall); |
| } |
| |
| bool PendingExtensionManager::AddFromExternalFile( |
| const std::string& id, |
| mojom::ManifestLocation install_source, |
| const base::Version& version, |
| int creation_flags, |
| bool mark_acknowledged) { |
| // TODO(skerner): AddFromSync() checks to see if the extension is |
| // installed, but this method assumes that the caller already |
| // made sure it is not installed. Make all AddFrom*() methods |
| // consistent. |
| const GURL& kUpdateUrl = GURL(); |
| static const bool kIsFromSync = false; |
| static const bool kRemoteInstall = false; |
| |
| return AddExtensionImpl(id, std::string(), kUpdateUrl, version, |
| &AlwaysInstall, kIsFromSync, install_source, |
| creation_flags, mark_acknowledged, kRemoteInstall); |
| } |
| |
| std::list<std::string> PendingExtensionManager::GetPendingIdsForUpdateCheck() |
| const { |
| std::list<std::string> result; |
| |
| for (const auto& it : pending_extensions_) { |
| ManifestLocation install_source = it.second.install_source(); |
| |
| // Some install sources read a CRX from the filesystem. They can |
| // not be fetched from an update URL, so don't include them in the |
| // set of ids. |
| if (install_source == ManifestLocation::kExternalPref || |
| install_source == ManifestLocation::kExternalRegistry || |
| install_source == ManifestLocation::kExternalPolicy) { |
| continue; |
| } |
| |
| result.push_back(it.first); |
| } |
| |
| return result; |
| } |
| |
| void PendingExtensionManager::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void PendingExtensionManager::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool PendingExtensionManager::AddExtensionImpl( |
| const std::string& id, |
| const std::string& install_parameter, |
| const GURL& update_url, |
| const base::Version& version, |
| PendingExtensionInfo::ShouldAllowInstallPredicate should_allow_install, |
| bool is_from_sync, |
| mojom::ManifestLocation install_source, |
| int creation_flags, |
| bool mark_acknowledged, |
| bool remote_install) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Demo mode apps are migrate to SWA. Old extensions are still installed from |
| // policy for old devices. Skip install these apps on devices up-to-date. |
| if (extension_misc::IsDemoModeChromeApp(id)) { |
| return false; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| PendingExtensionInfo info(id, install_parameter, update_url, version, |
| should_allow_install, is_from_sync, install_source, |
| creation_flags, mark_acknowledged, remote_install); |
| |
| auto it = pending_extensions_.find(id); |
| if (it != pending_extensions_.end()) { |
| const PendingExtensionInfo* pending = &(it->second); |
| // Bugs in this code will manifest as sporadic incorrect extension |
| // locations in situations where multiple install sources run at the |
| // same time. For example, on first login to a chrome os machine, an |
| // extension may be requested by sync and the default extension set. |
| // The following logging will help diagnose such issues. |
| VLOG(1) << "Extension id " << id |
| << " was entered for update more than once." |
| << " old location: " << pending->install_source() |
| << " new location: " << install_source |
| << " old version: " << GetVersionString(pending->version()) |
| << " new version: " << GetVersionString(version); |
| |
| // Never override an existing extension with an older version. Only |
| // extensions from local CRX files have a known version; extensions from an |
| // update URL will get the latest version. |
| |
| // If |pending| has the same or higher precedence than |info| then don't |
| // install |info| over |pending|. |
| if (pending->CompareTo(info) >= 0) { |
| return false; |
| } |
| |
| VLOG(1) << "Overwrite existing record."; |
| |
| it->second = std::move(info); |
| } else { |
| AddToMap(id, std::move(info)); |
| } |
| |
| return true; |
| } |
| |
| void PendingExtensionManager::AddForTesting( |
| PendingExtensionInfo pending_extension_info) { |
| std::string id = pending_extension_info.id(); |
| AddToMap(id, std::move(pending_extension_info)); |
| } |
| |
| void PendingExtensionManager::AddToMap(const std::string& id, |
| PendingExtensionInfo info) { |
| pending_extensions_.emplace(id, std::move(info)); |
| for (Observer& observer : observers_) { |
| observer.OnExtensionAdded(id); |
| } |
| } |
| |
| } // namespace extensions |