| // 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/ui/login/login_handler.h" |
| |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/password_manager/chrome_password_manager_client.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "components/autofill/core/browser/logging/log_manager.h" |
| #include "components/autofill/core/browser/logging/log_router.h" |
| #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" |
| #include "components/password_manager/core/browser/http_auth_manager.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/elide_url.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "net/base/auth.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_auth_scheme.h" |
| #include "net/http/http_transaction_factory.h" |
| #include "net/url_request/url_request_context.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "third_party/blink/public/common/loader/network_utils.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/text_elider.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "components/guest_view/browser/guest_view_base.h" |
| #include "extensions/browser/view_type_utils.h" // nogncheck |
| #include "extensions/common/mojom/view_type.mojom.h" |
| #endif |
| |
| using content::BrowserThread; |
| using content::NavigationController; |
| using content::WebContents; |
| using password_manager::PasswordForm; |
| |
| namespace { |
| |
| // All login handlers should be tracked in this global singleton. |
| using LoginHandlerVector = std::vector<base::WeakPtr<LoginHandler>>; |
| LoginHandlerVector& GetAllLoginHandlers() { |
| static base::NoDestructor<LoginHandlerVector> instance; |
| return *instance; |
| } |
| |
| } // namespace |
| |
| // ---------------------------------------------------------------------------- |
| // LoginHandler |
| |
| LoginHandler::LoginModelData::LoginModelData( |
| password_manager::HttpAuthManager* login_model, |
| const password_manager::PasswordForm& observed_form) |
| : model(login_model), form(observed_form) { |
| DCHECK(model); |
| } |
| |
| LoginHandler::~LoginHandler() { |
| auto& login_handlers = GetAllLoginHandlers(); |
| for (auto it = login_handlers.begin(); it != login_handlers.end(); ++it) { |
| if (it->get() == this) { |
| login_handlers.erase(it); |
| break; |
| } |
| } |
| |
| password_manager::HttpAuthManager* http_auth_manager = |
| GetHttpAuthManagerForLogin(); |
| if (http_auth_manager) { |
| http_auth_manager->OnPasswordFormDismissed(); |
| } |
| |
| if (!WasAuthHandled()) { |
| auth_required_callback_.Reset(); |
| } |
| } |
| |
| // static |
| std::vector<LoginHandler*> LoginHandler::GetAllLoginHandlersForTest() { |
| std::vector<LoginHandler*> output; |
| for (auto& weak_ptr : GetAllLoginHandlers()) { |
| if (weak_ptr) { |
| output.push_back(weak_ptr.get()); |
| } |
| } |
| return output; |
| } |
| |
| void LoginHandler::SetAuth(std::u16string_view username, |
| std::u16string_view password) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| std::unique_ptr<password_manager::BrowserSavePasswordProgressLogger> logger; |
| password_manager::PasswordManagerClient* client = |
| GetPasswordManagerClientFromWebContent(); |
| autofill::LogManager* log_manager = |
| client ? client->GetCurrentLogManager() : nullptr; |
| if (log_manager && log_manager->IsLoggingActive()) { |
| logger = |
| std::make_unique<password_manager::BrowserSavePasswordProgressLogger>( |
| log_manager); |
| logger->LogMessage( |
| autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD); |
| } |
| |
| bool already_handled = WasAuthHandled(); |
| if (logger) { |
| logger->LogBoolean( |
| autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED, |
| already_handled); |
| } |
| if (already_handled) { |
| return; |
| } |
| |
| // Tell the http-auth manager the credentials were submitted / accepted. |
| net::AuthCredentials credentials{std::u16string(username), |
| std::u16string(password)}; |
| if (password_manager::HttpAuthManager* httpauth_manager = |
| GetHttpAuthManagerForLogin()) { |
| password_form_.username_value = credentials.username(); |
| password_form_.password_value = credentials.password(); |
| httpauth_manager->OnPasswordFormSubmitted(password_form_); |
| if (logger) { |
| logger->LogPasswordForm( |
| autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM, |
| password_form_); |
| } |
| } |
| |
| content::LoginDelegate::LoginAuthRequiredCallback callback = |
| std::move(auth_required_callback_); |
| |
| // Calling NotifyAuthSupplied() first allows other LoginHandler instances to |
| // call CloseContents() before us. Closing dialogs in the opposite order as |
| // they were created avoids races where remaining dialogs in the same tab may |
| // be briefly displayed to the user before they are removed. |
| if (web_contents_) { |
| NotifyAuthSupplied(username, password); |
| } |
| CloseContents(); |
| std::move(callback).Run(std::move(credentials)); |
| } |
| |
| void LoginHandler::CancelAuth(bool notify_others) { |
| if (WasAuthHandled()) { |
| return; |
| } |
| |
| content::LoginDelegate::LoginAuthRequiredCallback callback = |
| std::move(auth_required_callback_); |
| |
| if (notify_others) { |
| NotifyAuthCancelled(); |
| } |
| |
| CloseContents(); |
| std::move(callback).Run(std::nullopt); |
| } |
| |
| LoginHandler::LoginHandler( |
| const net::AuthChallengeInfo& auth_info, |
| content::WebContents* web_contents, |
| content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback) |
| : web_contents_(web_contents->GetWeakPtr()), |
| auth_info_(auth_info), |
| auth_required_callback_(std::move(auth_required_callback)) { |
| GetAllLoginHandlers().push_back(weak_factory_.GetWeakPtr()); |
| } |
| |
| void LoginHandler::NotifyAuthNeeded() { |
| // Only used by tests. This is being refactored. https://crbug.com/1371177. |
| } |
| |
| void LoginHandler::NotifyAuthSupplied(std::u16string_view username, |
| std::u16string_view password) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(WasAuthHandled()); |
| |
| // Intentionally make a copy to avoid issues with iterator invalidation. |
| LoginHandlerVector vec = GetAllLoginHandlers(); |
| for (auto& weak_login_handler : vec) { |
| if (weak_login_handler && weak_login_handler.get() != this) { |
| weak_login_handler->OtherHandlerFinished(/*supplied=*/true, this, |
| username, password); |
| } |
| } |
| } |
| |
| void LoginHandler::NotifyAuthCancelled() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(WasAuthHandled()); |
| |
| // Intentionally make a copy to avoid issues with iterator invalidation. |
| LoginHandlerVector vec = GetAllLoginHandlers(); |
| for (auto& weak_login_handler : vec) { |
| if (weak_login_handler && weak_login_handler.get() != this) { |
| weak_login_handler->OtherHandlerFinished(/*supplied=*/false, this, |
| /*username=*/{}, |
| /*password=*/{}); |
| } |
| } |
| } |
| |
| void LoginHandler::OtherHandlerFinished(bool supplied, |
| LoginHandler* other_handler, |
| std::u16string_view username, |
| std::u16string_view password) { |
| // Break out early if we aren't interested in the notification. |
| if (!web_contents_ || WasAuthHandled()) { |
| return; |
| } |
| |
| // Only listen to notifications within a single BrowserContext. |
| if (other_handler->web_contents() && |
| other_handler->web_contents()->GetBrowserContext() != |
| web_contents_->GetBrowserContext()) { |
| return; |
| } |
| |
| // We should never dispatch to self. |
| DCHECK(other_handler != this); |
| |
| // Only handle notification for the identical auth info. When comparing |
| // AuthChallengeInfos, ignore path because the same credentials can be used |
| // for different paths. |
| if (!auth_info().MatchesExceptPath(other_handler->auth_info())) { |
| return; |
| } |
| |
| // Ignore login notification events from other StoragePartitions. |
| // TODO(crbug.com/40202416): Getting the StoragePartition from the WebContents |
| // is fine for now, but we'll need to plumb frame information to LoginHandler |
| // as part of removing the multi-WebContents architecture. |
| content::StoragePartition* source_partition = |
| other_handler->web_contents() ? other_handler->web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetStoragePartition() |
| : nullptr; |
| content::StoragePartition* partition = |
| web_contents()->GetPrimaryMainFrame()->GetStoragePartition(); |
| if (!source_partition || source_partition != partition) { |
| return; |
| } |
| |
| // Set or cancel the auth in this handler. Defer an event loop iteration to |
| // avoid potential reentrancy issues. |
| if (supplied) { |
| SetAuth(username, password); |
| } else { |
| CancelAuth(/*notify_others=*/true); |
| } |
| } |
| |
| password_manager::PasswordManagerClient* |
| LoginHandler::GetPasswordManagerClientFromWebContent() { |
| if (!web_contents_) { |
| return nullptr; |
| } |
| password_manager::PasswordManagerClient* client = |
| ChromePasswordManagerClient::FromWebContents(web_contents_.get()); |
| return client; |
| } |
| |
| password_manager::HttpAuthManager* LoginHandler::GetHttpAuthManagerForLogin() { |
| password_manager::PasswordManagerClient* client = |
| GetPasswordManagerClientFromWebContent(); |
| return client ? client->GetHttpAuthManager() : nullptr; |
| } |
| |
| // Returns whether authentication had been handled (SetAuth or CancelAuth). |
| bool LoginHandler::WasAuthHandled() const { |
| return auth_required_callback_.is_null(); |
| } |
| |
| // Closes the view_contents from the UI loop. |
| void LoginHandler::CloseContents() { |
| CloseDialog(); |
| } |
| |
| // static |
| std::string LoginHandler::GetSignonRealm( |
| const GURL& url, |
| const net::AuthChallengeInfo& auth_info) { |
| std::string signon_realm; |
| if (auth_info.is_proxy) { |
| // Historically we've been storing the signon realm for proxies using |
| // net::HostPortPair::ToString(). |
| net::HostPortPair host_port_pair = |
| net::HostPortPair::FromURL(auth_info.challenger.GetURL()); |
| signon_realm = host_port_pair.ToString(); |
| signon_realm.append("/"); |
| } else { |
| // Take scheme, host, and port from the url. |
| signon_realm = url.DeprecatedGetOriginAsURL().spec(); |
| // This ends with a "/". |
| } |
| signon_realm.append(auth_info.realm); |
| return signon_realm; |
| } |
| |
| // static |
| PasswordForm LoginHandler::MakeInputForPasswordManager( |
| const GURL& request_url, |
| const net::AuthChallengeInfo& auth_info) { |
| PasswordForm dialog_form; |
| if (base::EqualsCaseInsensitiveASCII(auth_info.scheme, |
| net::kBasicAuthScheme)) { |
| dialog_form.scheme = PasswordForm::Scheme::kBasic; |
| } else if (base::EqualsCaseInsensitiveASCII(auth_info.scheme, |
| net::kDigestAuthScheme)) { |
| dialog_form.scheme = PasswordForm::Scheme::kDigest; |
| } else { |
| dialog_form.scheme = PasswordForm::Scheme::kOther; |
| } |
| dialog_form.url = auth_info.challenger.GetURL(); |
| DCHECK(auth_info.is_proxy || |
| auth_info.challenger == url::SchemeHostPort(request_url)); |
| dialog_form.signon_realm = GetSignonRealm(dialog_form.url, auth_info); |
| return dialog_form; |
| } |
| |
| // static |
| void LoginHandler::GetDialogStrings(const GURL& request_url, |
| const net::AuthChallengeInfo& auth_info, |
| std::u16string* authority, |
| std::u16string* explanation) { |
| GURL authority_url; |
| |
| if (auth_info.is_proxy) { |
| *authority = l10n_util::GetStringFUTF16( |
| IDS_LOGIN_DIALOG_PROXY_AUTHORITY, |
| url_formatter::FormatUrlForSecurityDisplay( |
| auth_info.challenger.GetURL(), url_formatter::SchemeDisplay::SHOW)); |
| authority_url = auth_info.challenger.GetURL(); |
| } else { |
| *authority = url_formatter::FormatUrlForSecurityDisplay(request_url); |
| #if BUILDFLAG(IS_ANDROID) |
| // Android concatenates with a space rather than displaying on two separate |
| // lines, so it needs some surrounding text. |
| *authority = |
| l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_AUTHORITY, *authority); |
| #endif |
| authority_url = request_url; |
| } |
| |
| if (!network::IsUrlPotentiallyTrustworthy(authority_url)) { |
| // TODO(asanka): The string should be different for proxies and servers. |
| // http://crbug.com/620756 |
| *explanation = l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_NOT_PRIVATE); |
| } else { |
| explanation->clear(); |
| } |
| } |
| |
| void LoginHandler::ShowLoginPrompt(const GURL& request_url) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| std::u16string authority; |
| std::u16string explanation; |
| GetDialogStrings(request_url, auth_info(), &authority, &explanation); |
| |
| password_manager::HttpAuthManager* httpauth_manager = |
| GetHttpAuthManagerForLogin(); |
| |
| if (!httpauth_manager) { |
| // If there is no password manager, then show a view with no password |
| // manager hooked up. |
| BuildViewAndNotify(authority, explanation, nullptr); |
| return; |
| } |
| |
| password_manager::PasswordManagerClient* client = |
| GetPasswordManagerClientFromWebContent(); |
| autofill::LogManager* log_manager = |
| client ? client->GetCurrentLogManager() : nullptr; |
| if (log_manager && log_manager->IsLoggingActive()) { |
| password_manager::BrowserSavePasswordProgressLogger logger(log_manager); |
| logger.LogMessage( |
| autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD); |
| } |
| |
| PasswordForm observed_form( |
| MakeInputForPasswordManager(request_url, auth_info())); |
| LoginModelData model_data(httpauth_manager, observed_form); |
| BuildViewAndNotify(authority, explanation, &model_data); |
| } |
| |
| void LoginHandler::BuildViewAndNotify( |
| const std::u16string& authority, |
| const std::u16string& explanation, |
| LoginHandler::LoginModelData* login_model_data) { |
| if (login_model_data) { |
| password_form_ = *login_model_data->form; |
| } |
| bool success = BuildViewImpl(authority, explanation, login_model_data); |
| if (success) { |
| NotifyAuthNeeded(); |
| } else { |
| // CancelAuth results in synchronous destruction of `this`. As building the |
| // view happens synchronously, we dispatch the cancellation to avoid |
| // re-entrancy into the calling code. |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&LoginHandler::CancelAuth, weak_factory_.GetWeakPtr(), |
| /*notify_others=*/false)); |
| } |
| } |