| // Copyright 2020 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/ui/webui/chromeos/login/online_login_helper.h" |
| |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_platform_part.h" |
| #include "chrome/browser/chromeos/login/signin_partition_manager.h" |
| #include "chrome/browser/chromeos/login/ui/login_display_host_webui.h" |
| #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| #include "chromeos/dbus/util/version_loader.h" |
| #include "chromeos/login/auth/challenge_response/cert_utils.h" |
| #include "chromeos/login/auth/cryptohome_key_constants.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace chromeos { |
| namespace login { |
| namespace { |
| |
| const char kGAPSCookie[] = "GAPS"; |
| const char kOAUTHCodeCookie[] = "oauth_code"; |
| constexpr base::TimeDelta kCookieDelay = base::TimeDelta::FromSeconds(20); |
| |
| } // namespace |
| |
| GaiaContext::GaiaContext() {} |
| GaiaContext::GaiaContext(GaiaContext const&) = default; |
| |
| bool ExtractSamlPasswordAttributesEnabled() { |
| return base::FeatureList::IsEnabled(::features::kInSessionPasswordChange); |
| } |
| |
| base::OnceClosure GetStartSigninSession(::content::WebUI* web_ui, |
| LoadGaiaWithPartition callback) { |
| // Start a new session with SigninPartitionManager, generating a unique |
| // StoragePartition. |
| login::SigninPartitionManager* signin_partition_manager = |
| login::SigninPartitionManager::Factory::GetForBrowserContext( |
| Profile::FromWebUI(web_ui)); |
| |
| auto partition_call = |
| base::BindOnce(&login::SigninPartitionManager::StartSigninSession, |
| base::Unretained(signin_partition_manager), |
| web_ui->GetWebContents(), std::move(callback)); |
| return partition_call; |
| } |
| |
| void SetCookieForPartition( |
| const login::GaiaContext& context, |
| login::SigninPartitionManager* signin_partition_manager, |
| OnSetCookieForLoadGaiaWithPartition callback) { |
| content::StoragePartition* partition = |
| signin_partition_manager->GetCurrentStoragePartition(); |
| if (!partition) |
| return; |
| |
| // Note: The CanonicalCookie created here is not Secure. This is fine because |
| // it's being set into a different StoragePartition than the user's actual |
| // profile. The SetCanonicalCookie call will succeed regardless of the scheme |
| // of |gaia_url| since there are no scheme restrictions since the cookie is |
| // not Secure, and there is no preexisting Secure cookie in the profile that |
| // would preclude updating it insecurely. |gaia_url| is usually secure, and |
| // only insecure in local testing. |
| |
| std::string gaps_cookie_value(kGAPSCookie); |
| gaps_cookie_value += "=" + context.gaps_cookie; |
| const GURL& gaia_url = GaiaUrls::GetInstance()->gaia_url(); |
| std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create( |
| gaia_url, gaps_cookie_value, base::Time::Now(), |
| base::nullopt /* server_time */)); |
| if (!cc) |
| return; |
| |
| const net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| partition->GetCookieManagerForBrowserProcess()->SetCanonicalCookie( |
| *cc.get(), gaia_url, options, std::move(callback)); |
| } |
| |
| user_manager::UserType GetUsertypeFromServicesString( |
| const ::login::StringList& services) { |
| bool is_child = false; |
| const bool support_usm = |
| base::FeatureList::IsEnabled(::features::kCrOSEnableUSMUserService); |
| using KnownFlags = base::flat_set<std::string>; |
| const KnownFlags known_flags = |
| support_usm ? KnownFlags({"uca", "usm"}) : KnownFlags({"uca"}); |
| |
| for (const std::string& item : services) { |
| if (known_flags.find(item) != known_flags.end()) { |
| is_child = true; |
| break; |
| } |
| } |
| |
| return is_child ? user_manager::USER_TYPE_CHILD |
| : user_manager::USER_TYPE_REGULAR; |
| } |
| |
| bool BuildUserContextForGaiaSignIn( |
| user_manager::UserType user_type, |
| const AccountId& account_id, |
| bool using_saml, |
| bool using_saml_api, |
| const std::string& password, |
| const SamlPasswordAttributes& password_attributes, |
| const LoginClientCertUsageObserver& |
| extension_provided_client_cert_usage_observer, |
| UserContext* user_context, |
| std::string* error_message) { |
| *user_context = UserContext(user_type, account_id); |
| if (using_saml && |
| extension_provided_client_cert_usage_observer.ClientCertsWereUsed()) { |
| scoped_refptr<net::X509Certificate> saml_client_cert; |
| std::vector<ChallengeResponseKey::SignatureAlgorithm> signature_algorithms; |
| std::string extension_id; |
| if (!extension_provided_client_cert_usage_observer.GetOnlyUsedClientCert( |
| &saml_client_cert, &signature_algorithms, &extension_id)) { |
| *error_message = l10n_util::GetStringUTF8( |
| IDS_CHALLENGE_RESPONSE_AUTH_MULTIPLE_CLIENT_CERTS_ERROR); |
| return false; |
| } |
| ChallengeResponseKey challenge_response_key; |
| if (!ExtractChallengeResponseKeyFromCert( |
| *saml_client_cert, signature_algorithms, &challenge_response_key)) { |
| *error_message = l10n_util::GetStringUTF8( |
| IDS_CHALLENGE_RESPONSE_AUTH_INVALID_CLIENT_CERT_ERROR); |
| return false; |
| } |
| challenge_response_key.set_extension_id(extension_id); |
| user_context->GetMutableChallengeResponseKeys()->push_back( |
| challenge_response_key); |
| } else { |
| Key key(password); |
| key.SetLabel(kCryptohomeGaiaKeyLabel); |
| user_context->SetKey(key); |
| user_context->SetPasswordKey(Key(password)); |
| } |
| user_context->SetAuthFlow(using_saml |
| ? UserContext::AUTH_FLOW_GAIA_WITH_SAML |
| : UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML); |
| if (using_saml) { |
| user_context->SetIsUsingSamlPrincipalsApi(using_saml_api); |
| if (ExtractSamlPasswordAttributesEnabled()) { |
| user_context->SetSamlPasswordAttributes(password_attributes); |
| } |
| } |
| return true; |
| } |
| |
| } // namespace login |
| |
| OnlineLoginHelper::OnlineLoginHelper( |
| std::string signin_partition_name, |
| login::SigninPartitionManager* signin_partition_manager, |
| OnCookieTimeoutCallback on_cookie_timeout_callback, |
| CompleteLoginCallback complete_login_callback) |
| : signin_partition_name_(signin_partition_name), |
| signin_partition_manager_(signin_partition_manager), |
| on_cookie_timeout_callback_(std::move(on_cookie_timeout_callback)), |
| complete_login_callback_(std::move(complete_login_callback)) {} |
| |
| OnlineLoginHelper::~OnlineLoginHelper() {} |
| |
| void OnlineLoginHelper::SetUserContext( |
| std::unique_ptr<UserContext> pending_user_context) { |
| pending_user_context_ = std::move(pending_user_context); |
| } |
| |
| void OnlineLoginHelper::RequestCookiesAndCompleteAuthentication() { |
| content::StoragePartition* partition = |
| signin_partition_manager_->GetCurrentStoragePartition(); |
| if (!partition) |
| return; |
| |
| // Validity check that partition did not change during login flow. |
| DCHECK_EQ(signin_partition_manager_->GetCurrentStoragePartitionName(), |
| signin_partition_name_); |
| |
| network::mojom::CookieManager* cookie_manager = |
| partition->GetCookieManagerForBrowserProcess(); |
| if (!oauth_code_listener_.is_bound()) { |
| // Set listener before requesting the cookies to avoid race conditions. |
| cookie_manager->AddCookieChangeListener( |
| GaiaUrls::GetInstance()->gaia_url(), login::kOAUTHCodeCookie, |
| oauth_code_listener_.BindNewPipeAndPassRemote()); |
| cookie_waiting_timer_ = std::make_unique<base::OneShotTimer>(); |
| cookie_waiting_timer_->Start( |
| FROM_HERE, login::kCookieDelay, |
| base::BindOnce(&OnlineLoginHelper::OnCookieWaitTimeout, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| const net::CookieOptions cookie_options = |
| net::CookieOptions::MakeAllInclusive(); |
| cookie_manager->GetCookieList( |
| GaiaUrls::GetInstance()->gaia_url(), cookie_options, |
| base::BindOnce(&OnlineLoginHelper::OnGetCookiesForCompleteAuthentication, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnlineLoginHelper::OnCookieChange(const net::CookieChangeInfo& change) { |
| RequestCookiesAndCompleteAuthentication(); |
| } |
| |
| void OnlineLoginHelper::OnCookieWaitTimeout() { |
| DCHECK(pending_user_context_); |
| pending_user_context_.reset(); |
| oauth_code_listener_.reset(); |
| cookie_waiting_timer_.reset(); |
| std::move(on_cookie_timeout_callback_).Run(); |
| } |
| |
| void OnlineLoginHelper::OnGetCookiesForCompleteAuthentication( |
| const net::CookieAccessResultList& cookies, |
| const net::CookieAccessResultList& excluded_cookies) { |
| std::string auth_code, gaps_cookie; |
| for (const auto& cookie_with_access_result : cookies) { |
| const auto& cookie = cookie_with_access_result.cookie; |
| if (cookie.Name() == login::kOAUTHCodeCookie) |
| auth_code = cookie.Value(); |
| else if (cookie.Name() == login::kGAPSCookie) |
| gaps_cookie = cookie.Value(); |
| } |
| |
| if (auth_code.empty()) { |
| // Will try again from onCookieChange. |
| return; |
| } |
| |
| DCHECK(pending_user_context_); |
| UserContext user_context = *pending_user_context_; |
| pending_user_context_.reset(); |
| oauth_code_listener_.reset(); |
| cookie_waiting_timer_.reset(); |
| |
| user_context.SetAuthCode(auth_code); |
| if (!gaps_cookie.empty()) |
| user_context.SetGAPSCookie(gaps_cookie); |
| |
| std::move(complete_login_callback_).Run(user_context); |
| } |
| |
| } // namespace chromeos |