| // Copyright 2012 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/api/identity/identity_api.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/lazy_instance.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/app_mode/app_mode_utils.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/account_consistency_mode_manager.h" |
| #include "chrome/browser/signin/chrome_signin_client_factory.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_features.h" |
| #include "chrome/common/extensions/api/identity.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/primary_account_change_event.h" |
| #include "extensions/browser/extension_function_dispatcher.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_l10n_util.h" |
| #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" |
| #include "chrome/browser/ui/signin/signin_view_controller.h" // nogncheck crbug.com/423799622 |
| #endif |
| |
| using signin::ConsentLevel; |
| using signin::PrimaryAccountChangeEvent; |
| |
| namespace extensions { |
| |
| namespace { |
| const char kIdentityGaiaIdPref[] = "identity_gaia_id"; |
| } |
| |
| IdentityAPI::IdentityAPI(content::BrowserContext* context) |
| : IdentityAPI(Profile::FromBrowserContext(context), |
| IdentityManagerFactory::GetForProfile( |
| Profile::FromBrowserContext(context)), |
| ExtensionPrefs::Get(context), |
| EventRouter::Get(context)) {} |
| |
| IdentityAPI::~IdentityAPI() = default; |
| |
| IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; } |
| |
| IdentityTokenCache* IdentityAPI::token_cache() { |
| return &token_cache_; |
| } |
| |
| void IdentityAPI::SetGaiaIdForExtension(const std::string& extension_id, |
| const GaiaId& gaia_id) { |
| DCHECK(!gaia_id.empty()); |
| extension_prefs_->UpdateExtensionPref(extension_id, kIdentityGaiaIdPref, |
| base::Value(gaia_id.ToString())); |
| } |
| |
| std::optional<GaiaId> IdentityAPI::GetGaiaIdForExtension( |
| const std::string& extension_id) { |
| std::string gaia_id; |
| if (!extension_prefs_->ReadPrefAsString(extension_id, kIdentityGaiaIdPref, |
| &gaia_id)) { |
| return std::nullopt; |
| } |
| return GaiaId(gaia_id); |
| } |
| |
| void IdentityAPI::EraseGaiaIdForExtension(const std::string& extension_id) { |
| extension_prefs_->UpdateExtensionPref(extension_id, kIdentityGaiaIdPref, |
| std::nullopt); |
| } |
| |
| void IdentityAPI::EraseStaleGaiaIdsForAllExtensions() { |
| // Refresh tokens haven't been loaded yet. Wait for OnRefreshTokensLoaded() to |
| // fire. |
| if (!identity_manager_->AreRefreshTokensLoaded()) |
| return; |
| auto accounts = GetAccountsWithRefreshTokensForExtensions(); |
| for (const ExtensionId& extension_id : extension_prefs_->GetExtensions()) { |
| std::optional<GaiaId> gaia_id = GetGaiaIdForExtension(extension_id); |
| if (!gaia_id) |
| continue; |
| if (!base::Contains(accounts, *gaia_id, &CoreAccountInfo::gaia)) { |
| EraseGaiaIdForExtension(extension_id); |
| } |
| } |
| } |
| |
| void IdentityAPI::Shutdown() { |
| on_shutdown_callback_list_.Notify(); |
| identity_manager_->RemoveObserver(this); |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI>>:: |
| DestructorAtExit g_identity_api_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { |
| return g_identity_api_factory.Pointer(); |
| } |
| |
| base::CallbackListSubscription IdentityAPI::RegisterOnShutdownCallback( |
| base::OnceClosure cb) { |
| return on_shutdown_callback_list_.Add(std::move(cb)); |
| } |
| |
| bool IdentityAPI::AreExtensionsRestrictedToPrimaryAccount() { |
| return !AccountConsistencyModeManager::IsDiceEnabledForProfile(profile_) && |
| !AccountConsistencyModeManager::IsMirrorEnabledForProfile(profile_); |
| } |
| |
| bool IdentityAPI::HasAccessToChromeAccounts() const { |
| return identity_manager_->HasPrimaryAccount(ConsentLevel::kSignin); |
| } |
| |
| std::vector<CoreAccountInfo> |
| IdentityAPI::GetAccountsWithRefreshTokensForExtensions() { |
| if (!HasAccessToChromeAccounts()) { |
| return {}; |
| } |
| return identity_manager_->GetAccountsWithRefreshTokens(); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| void IdentityAPI::MaybeShowChromeSigninDialog( |
| const std::u16string& extension_name_for_display, |
| base::OnceClosure on_complete) { |
| if (HasAccessToChromeAccounts() || |
| identity_manager_->GetAccountsWithRefreshTokens().empty()) { |
| DVLOG(1) << "The user is not signed in on the web!"; |
| std::move(on_complete).Run(); |
| return; |
| } |
| |
| if (is_chrome_signin_dialog_open_) { |
| DVLOG(1) << "Chrome sign in dialog is already open, extensions are not " |
| "allowed to trigger more than one"; |
| on_chrome_signin_dialog_completed_.push_back(std::move(on_complete)); |
| return; |
| } |
| |
| // Used in unittests to avoid creating a browser. |
| if (skip_ui_for_testing_callback_) { |
| HandleSkipUIForTesting(std::move(on_complete)); // IN-TEST |
| return; |
| } |
| |
| chrome::ScopedTabbedBrowserDisplayer displayer(profile_); |
| Browser* browser = displayer.browser(); |
| if (!browser) { |
| DVLOG(1) << "Could not create a browser to show Extensions Chrome Sign in " |
| "dialog."; |
| std::move(on_complete).Run(); |
| return; |
| } |
| on_chrome_signin_dialog_completed_.push_back(std::move(on_complete)); |
| is_chrome_signin_dialog_open_ = true; |
| browser->GetFeatures() |
| .signin_view_controller() |
| ->MaybeShowChromeSigninDialogForExtensions( |
| extension_name_for_display, |
| base::BindOnce(&IdentityAPI::OnChromeSigninDialogDestroyed, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void IdentityAPI::OnChromeSigninDialogDestroyed() { |
| is_chrome_signin_dialog_open_ = false; |
| std::vector<base::OnceClosure> callbacks; |
| std::swap(on_chrome_signin_dialog_completed_, callbacks); |
| for (auto& callback : callbacks) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| void IdentityAPI::HandleSkipUIForTesting(base::OnceClosure on_complete) { |
| is_chrome_signin_dialog_open_ = true; |
| on_chrome_signin_dialog_completed_.push_back(std::move(on_complete)); |
| // Allow tests to complete the flow. |
| std::move(skip_ui_for_testing_callback_) |
| .Run(base::BindOnce(&IdentityAPI::OnChromeSigninDialogDestroyed, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| #endif |
| |
| IdentityAPI::IdentityAPI(Profile* profile, |
| signin::IdentityManager* identity_manager, |
| ExtensionPrefs* extension_prefs, |
| EventRouter* event_router) |
| : profile_(profile), |
| identity_manager_(identity_manager), |
| extension_prefs_(extension_prefs), |
| event_router_(event_router) { |
| identity_manager_->AddObserver(this); |
| EraseStaleGaiaIdsForAllExtensions(); |
| } |
| |
| void IdentityAPI::OnPrimaryAccountChanged( |
| const PrimaryAccountChangeEvent& event_details) { |
| switch (event_details.GetEventTypeFor(ConsentLevel::kSignin)) { |
| case signin::PrimaryAccountChangeEvent::Type::kNone: |
| break; |
| case signin::PrimaryAccountChangeEvent::Type::kSet: |
| for (auto& account : GetAccountsWithRefreshTokensForExtensions()) { |
| OnRefreshTokenUpdatedForAccount(account); |
| } |
| break; |
| case signin::PrimaryAccountChangeEvent::Type::kCleared: |
| EraseStaleGaiaIdsForAllExtensions(); |
| base::flat_set<GaiaId> tracked_accounts; |
| std::swap(tracked_accounts, accounts_known_to_extensions_); |
| for (auto& account_id : tracked_accounts) { |
| FireOnAccountSignInChanged(account_id, /*is_signed_in=*/false); |
| } |
| break; |
| } |
| } |
| |
| void IdentityAPI::OnRefreshTokensLoaded() { |
| EraseStaleGaiaIdsForAllExtensions(); |
| } |
| |
| void IdentityAPI::OnRefreshTokenUpdatedForAccount( |
| const CoreAccountInfo& account_info) { |
| // Refresh tokens are sometimes made available in contexts where |
| // AccountTrackerService is not tracking the account in question. Bail out in |
| // these cases. |
| if (!HasAccessToChromeAccounts() || account_info.gaia.empty()) { |
| return; |
| } |
| accounts_known_to_extensions_.insert(account_info.gaia); |
| FireOnAccountSignInChanged(account_info.gaia, true); |
| } |
| |
| void IdentityAPI::OnExtendedAccountInfoRemoved( |
| const AccountInfo& account_info) { |
| DCHECK(!account_info.gaia.empty()); |
| EraseStaleGaiaIdsForAllExtensions(); |
| |
| auto it = accounts_known_to_extensions_.find(account_info.gaia); |
| if (it == accounts_known_to_extensions_.end()) { |
| // Account unknown to Extensions. |
| return; |
| } |
| accounts_known_to_extensions_.erase(it); |
| FireOnAccountSignInChanged(account_info.gaia, false); |
| } |
| |
| void IdentityAPI::FireOnAccountSignInChanged(const GaiaId& gaia_id, |
| bool is_signed_in) { |
| CHECK(!gaia_id.empty()); |
| api::identity::AccountInfo api_account_info; |
| api_account_info.id = gaia_id.ToString(); |
| |
| auto args = |
| api::identity::OnSignInChanged::Create(api_account_info, is_signed_in); |
| std::unique_ptr<Event> event(new Event( |
| events::IDENTITY_ON_SIGN_IN_CHANGED, |
| api::identity::OnSignInChanged::kEventName, std::move(args), profile_)); |
| |
| if (on_signin_changed_callback_for_testing_) |
| on_signin_changed_callback_for_testing_.Run(event.get()); |
| |
| event_router_->BroadcastEvent(std::move(event)); |
| } |
| |
| template <> |
| void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() { |
| DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); |
| |
| DependsOn(ChromeSigninClientFactory::GetInstance()); |
| DependsOn(IdentityManagerFactory::GetInstance()); |
| } |
| |
| } // namespace extensions |