blob: bce290439e124c3427fd2ac77bceab407fdf62cc [file] [log] [blame]
// Copyright 2013 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/gaia_screen_handler.h"
#include <memory>
#include "ash/public/cpp/ash_features.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/guid.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/login/lock_screen_utils.h"
#include "chrome/browser/chromeos/login/reauth_stats.h"
#include "chrome/browser/chromeos/login/screens/network_error.h"
#include "chrome/browser/chromeos/login/signin_partition_manager.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_webui.h"
#include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager_util.h"
#include "chrome/browser/chromeos/net/network_portal_detector_impl.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/temp_certs_cache_nss.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/chromeos/login/active_directory_password_change_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
#include "chrome/browser/ui/webui/metrics_handler.h"
#include "chrome/browser/ui/webui/signin/signin_utils.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/login/auth/authpolicy_login_helper.h"
#include "chromeos/login/auth/user_context.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/system/devicetype.h"
#include "chromeos/system/version_loader.h"
#include "components/login/localized_values_builder.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_urls.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/base/ime/chromeos/input_method_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
using content::BrowserThread;
namespace em = enterprise_management;
namespace chromeos {
namespace {
const char kJsScreenPath[] = "login.GaiaSigninScreen";
const char kAuthIframeParentName[] = "signin-frame";
const char kRestrictiveProxyURL[] = "https://www.google.com/generate_204";
const char kEndpointGen[] = "1.0";
// The possible modes that the Gaia signin screen can be in.
enum GaiaScreenMode {
// Default Gaia authentication will be used.
GAIA_SCREEN_MODE_DEFAULT = 0,
// Gaia offline mode will be used.
GAIA_SCREEN_MODE_OFFLINE = 1,
// An interstitial page will be used before SAML redirection.
GAIA_SCREEN_MODE_SAML_INTERSTITIAL = 2,
// Offline UI for Active Directory authentication.
GAIA_SCREEN_MODE_AD = 3,
};
policy::DeviceMode GetDeviceMode() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
return connector->GetDeviceMode();
}
GaiaScreenMode GetGaiaScreenMode(const std::string& email, bool use_offline) {
if (use_offline)
return GAIA_SCREEN_MODE_OFFLINE;
if (GetDeviceMode() == policy::DEVICE_MODE_ENTERPRISE_AD)
return GAIA_SCREEN_MODE_AD;
int authentication_behavior = 0;
CrosSettings::Get()->GetInteger(kLoginAuthenticationBehavior,
&authentication_behavior);
if (authentication_behavior ==
em::LoginAuthenticationBehaviorProto::SAML_INTERSTITIAL) {
if (email.empty())
return GAIA_SCREEN_MODE_SAML_INTERSTITIAL;
// If there's a populated email, we must check first that this user is using
// SAML in order to decide whether to show the interstitial page.
const user_manager::User* user = user_manager::UserManager::Get()->FindUser(
user_manager::known_user::GetAccountId(email, std::string() /* id */,
AccountType::UNKNOWN));
if (user && user->using_saml())
return GAIA_SCREEN_MODE_SAML_INTERSTITIAL;
}
return GAIA_SCREEN_MODE_DEFAULT;
}
std::string GetEnterpriseDisplayDomain() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
return connector->GetEnterpriseDisplayDomain();
}
std::string GetEnterpriseEnrollmentDomain() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
return connector->GetEnterpriseEnrollmentDomain();
}
std::string GetRealm() {
policy::BrowserPolicyConnectorChromeOS* connector =
g_browser_process->platform_part()->browser_policy_connector_chromeos();
return connector->GetRealm();
}
std::string GetChromeType() {
switch (chromeos::GetDeviceType()) {
case chromeos::DeviceType::kChromebox:
return "chromebox";
case chromeos::DeviceType::kChromebase:
return "chromebase";
case chromeos::DeviceType::kChromebit:
return "chromebit";
case chromeos::DeviceType::kChromebook:
return "chromebook";
default:
return "chromedevice";
}
}
void UpdateAuthParams(base::DictionaryValue* params,
bool is_restrictive_proxy) {
CrosSettings* cros_settings = CrosSettings::Get();
bool allow_new_user = true;
cros_settings->GetBoolean(kAccountsPrefAllowNewUser, &allow_new_user);
params->SetBoolean(
"guestSignin",
chrome_user_manager_util::IsGuestSessionAllowed(cros_settings));
// nosignup flow if new users are not allowed.
if (!allow_new_user || is_restrictive_proxy)
params->SetString("flow", "nosignup");
params->SetBoolean("supervisedUsersCanCreate", false);
// Now check whether we're in multi-profiles user adding scenario and
// disable GAIA right panel features if that's the case.
if (UserAddingScreen::Get()->IsRunning()) {
params->SetBoolean("guestSignin", false);
params->SetBoolean("supervisedUsersCanCreate", false);
}
}
void RecordSAMLScrapingVerificationResultInHistogram(bool success) {
UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.Scraping.VerificationResult", success);
}
void PushFrontIMIfNotExists(const std::string& input_method,
std::vector<std::string>* input_methods) {
if (input_method.empty())
return;
if (!base::ContainsValue(*input_methods, input_method))
input_methods->insert(input_methods->begin(), input_method);
}
bool IsOnline(NetworkPortalDetector::CaptivePortalStatus status) {
return status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE;
}
void GetVersionAndConsent(std::string* out_version, bool* out_consent) {
*out_version = version_loader::GetVersion(version_loader::VERSION_SHORT);
*out_consent = GoogleUpdateSettings::GetCollectStatsConsent();
}
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"});
size_t i = 0;
for (const std::string& item : services) {
if (known_flags.find(item) != known_flags.end()) {
is_child = true;
}
++i;
}
return is_child ? user_manager::USER_TYPE_CHILD
: user_manager::USER_TYPE_REGULAR;
}
user_manager::UserType CalculateUserType(const AccountId& account_id) {
if (user_manager::UserManager::Get()->IsSupervisedAccountId(account_id))
return user_manager::USER_TYPE_SUPERVISED;
if (account_id.GetAccountType() == AccountType::ACTIVE_DIRECTORY)
return user_manager::USER_TYPE_ACTIVE_DIRECTORY;
return user_manager::USER_TYPE_REGULAR;
}
std::string GetAdErrorMessage(authpolicy::ErrorType error) {
switch (error) {
case authpolicy::ERROR_NETWORK_PROBLEM:
return l10n_util::GetStringUTF8(IDS_AD_AUTH_NETWORK_ERROR);
case authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE:
return l10n_util::GetStringUTF8(IDS_AD_AUTH_NOT_SUPPORTED_ENCRYPTION);
default:
DLOG(WARNING) << "Unhandled error code: " << error;
return l10n_util::GetStringUTF8(IDS_AD_AUTH_UNKNOWN_ERROR);
}
}
} // namespace
// A class that's used to specify the way how Gaia should be loaded.
struct GaiaScreenHandler::GaiaContext {
GaiaContext();
// Forces Gaia to reload.
bool force_reload = false;
// Whether Gaia should be loaded in offline mode.
bool use_offline = false;
// Email of the current user.
std::string email;
// GAIA ID of the current user.
std::string gaia_id;
// GAPS cookie.
std::string gaps_cookie;
};
GaiaScreenHandler::GaiaContext::GaiaContext() {}
GaiaScreenHandler::GaiaScreenHandler(
CoreOobeView* core_oobe_view,
const scoped_refptr<NetworkStateInformer>& network_state_informer,
ActiveDirectoryPasswordChangeScreenHandler*
active_directory_password_change_screen_handler)
: BaseScreenHandler(kScreenId),
network_state_informer_(network_state_informer),
core_oobe_view_(core_oobe_view),
active_directory_password_change_screen_handler_(
active_directory_password_change_screen_handler),
weak_factory_(this) {
DCHECK(network_state_informer_.get());
set_call_js_prefix(kJsScreenPath);
}
GaiaScreenHandler::~GaiaScreenHandler() {
if (network_portal_detector_)
network_portal_detector_->RemoveObserver(this);
}
void GaiaScreenHandler::MaybePreloadAuthExtension() {
// We shall not have network portal detector initialized, which unnecessarily
// polls captive portal checking URL if we don't need to load gaia. See
// go/bad-portal for more context.
if (!signin_screen_handler_->ShouldLoadGaia())
return;
VLOG(1) << "MaybePreloadAuthExtension";
if (!network_portal_detector_) {
NetworkPortalDetectorImpl* detector = new NetworkPortalDetectorImpl(
g_browser_process->system_network_context_manager()
->GetURLLoaderFactory());
detector->set_portal_test_url(GURL(kRestrictiveProxyURL));
network_portal_detector_.reset(detector);
network_portal_detector_->AddObserver(this);
network_portal_detector_->Enable(true);
}
// If cookies clearing was initiated or |dns_clear_task_running_| then auth
// extension showing has already been initiated and preloading is pointless.
if (!gaia_silent_load_ && !cookies_cleared_ && !dns_clear_task_running_ &&
network_state_informer_->state() == NetworkStateInformer::ONLINE) {
gaia_silent_load_ = true;
gaia_silent_load_network_ = network_state_informer_->network_path();
LoadAuthExtension(true /* force */, false /* offline */);
}
}
void GaiaScreenHandler::DisableRestrictiveProxyCheckForTest() {
disable_restrictive_proxy_check_for_test_ = true;
}
void GaiaScreenHandler::LoadGaia(const GaiaContext& context) {
// Start a new session with SigninPartitionManager, generating a unique
// StoragePartition.
login::SigninPartitionManager* signin_partition_manager =
login::SigninPartitionManager::Factory::GetForBrowserContext(
Profile::FromWebUI(web_ui()));
signin_partition_manager->StartSigninSession(
web_ui()->GetWebContents(),
base::BindOnce(&GaiaScreenHandler::LoadGaiaWithPartition,
weak_factory_.GetWeakPtr(), context));
}
void GaiaScreenHandler::LoadGaiaWithPartition(
const GaiaContext& context,
const std::string& partition_name) {
std::unique_ptr<std::string> version = std::make_unique<std::string>();
std::unique_ptr<bool> consent = std::make_unique<bool>();
base::OnceClosure get_version_and_consent =
base::BindOnce(&GetVersionAndConsent, base::Unretained(version.get()),
base::Unretained(consent.get()));
base::OnceClosure load_gaia = base::BindOnce(
&GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent,
weak_factory_.GetWeakPtr(), context, partition_name,
base::Owned(version.release()), base::Owned(consent.release()));
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
std::move(get_version_and_consent), std::move(load_gaia));
}
void GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent(
const GaiaContext& context,
const std::string& partition_name,
const std::string* platform_version,
const bool* collect_stats_consent) {
base::DictionaryValue params;
params.SetBoolean("forceReload", context.force_reload);
params.SetString("gaiaId", context.gaia_id);
params.SetBoolean("readOnlyEmail", true);
params.SetString("email", context.email);
params.SetString("gapsCookie", context.gaps_cookie);
UpdateAuthParams(&params, IsRestrictiveProxy());
GaiaScreenMode screen_mode = GetGaiaScreenMode(context.email,
context.use_offline);
params.SetInteger("screenMode", screen_mode);
if (screen_mode == GAIA_SCREEN_MODE_AD && !authpolicy_login_helper_)
authpolicy_login_helper_ = std::make_unique<AuthPolicyLoginHelper>();
if (screen_mode != GAIA_SCREEN_MODE_OFFLINE) {
const std::string app_locale = g_browser_process->GetApplicationLocale();
if (!app_locale.empty())
params.SetString("hl", app_locale);
}
std::string realm(GetRealm());
if (!realm.empty()) {
params.SetString("realm", realm);
}
const std::string enterprise_display_domain(GetEnterpriseDisplayDomain());
const std::string enterprise_enrollment_domain(
GetEnterpriseEnrollmentDomain());
if (!enterprise_display_domain.empty())
params.SetString("enterpriseDisplayDomain", enterprise_display_domain);
if (!enterprise_enrollment_domain.empty()) {
params.SetString("enterpriseEnrollmentDomain",
enterprise_enrollment_domain);
}
params.SetBoolean("enterpriseManagedDevice",
g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->IsEnterpriseManaged());
const AccountId& owner_account_id =
user_manager::UserManager::Get()->GetOwnerAccountId();
params.SetBoolean("hasDeviceOwner", owner_account_id.is_valid());
if (owner_account_id.is_valid()) {
// Some Autotest policy tests appear to wipe the user list in Local State
// but preserve a policy file referencing an owner: https://crbug.com/850139
const user_manager::User* owner_user =
user_manager::UserManager::Get()->FindUser(owner_account_id);
if (owner_user &&
owner_user->GetType() == user_manager::UserType::USER_TYPE_CHILD) {
params.SetString("obfuscatedOwnerId", owner_account_id.GetGaiaId());
}
}
params.SetString("chromeType", GetChromeType());
params.SetString("clientId",
GaiaUrls::GetInstance()->oauth2_chrome_client_id());
params.SetString("clientVersion", version_info::GetVersionNumber());
if (!platform_version->empty())
params.SetString("platformVersion", *platform_version);
params.SetString("releaseChannel", chrome::GetChannelName());
params.SetString("endpointGen", kEndpointGen);
std::string email_domain;
if (CrosSettings::Get()->GetString(kAccountsPrefLoginScreenDomainAutoComplete,
&email_domain) &&
!email_domain.empty()) {
params.SetString("emailDomain", email_domain);
}
params.SetString("gaiaUrl", GaiaUrls::GetInstance()->gaia_url().spec());
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kCrosGaiaApiV1)) {
params.SetString("chromeOSApiVersion", "1");
} else {
// This enables GLIF MM UI for the online Gaia screen by default.
// (see https://crbug.com/709244 ).
params.SetString("chromeOSApiVersion", "2");
}
// We only send |chromeos_board| Gaia URL parameter if user has opted into
// sending device statistics.
if (*collect_stats_consent)
params.SetString("lsbReleaseBoard", base::SysInfo::GetLsbReleaseBoard());
params.SetString("webviewPartitionName", partition_name);
frame_state_ = FRAME_STATE_LOADING;
CallJS("loadAuthExtension", params);
}
void GaiaScreenHandler::ReloadGaia(bool force_reload) {
if (frame_state_ == FRAME_STATE_LOADING && !force_reload) {
VLOG(1) << "Skipping reloading of Gaia since gaia is loading.";
return;
}
NetworkStateInformer::State state = network_state_informer_->state();
if (state != NetworkStateInformer::ONLINE &&
!signin_screen_handler_->proxy_auth_dialog_need_reload_) {
VLOG(1) << "Skipping reloading of Gaia since network state="
<< NetworkStateInformer::StatusString(state);
return;
}
signin_screen_handler_->proxy_auth_dialog_need_reload_ = false;
VLOG(1) << "Reloading Gaia.";
frame_state_ = FRAME_STATE_LOADING;
LoadAuthExtension(force_reload, false /* offline */);
}
void GaiaScreenHandler::MonitorOfflineIdle(bool is_online) {
CallJS("monitorOfflineIdle", is_online);
}
void GaiaScreenHandler::DeclareLocalizedValues(
::login::LocalizedValuesBuilder* builder) {
builder->Add("signinScreenTitle", IDS_SIGNIN_SCREEN_TITLE_TAB_PROMPT);
builder->Add("guestSignin", IDS_BROWSE_WITHOUT_SIGNING_IN_HTML);
builder->Add("backButton", IDS_ACCNAME_BACK);
builder->Add("closeButton", IDS_CLOSE);
builder->Add("whitelistErrorConsumer", IDS_LOGIN_ERROR_WHITELIST);
builder->Add("whitelistErrorEnterprise",
IDS_ENTERPRISE_LOGIN_ERROR_WHITELIST);
builder->Add("tryAgainButton", IDS_WHITELIST_ERROR_TRY_AGAIN_BUTTON);
builder->Add("learnMoreButton", IDS_LEARN_MORE);
builder->Add("gaiaLoading", IDS_LOGIN_GAIA_LOADING_MESSAGE);
// Strings used by the SAML fatal error dialog.
builder->Add("fatalErrorMessageNoAccountDetails",
IDS_LOGIN_FATAL_ERROR_NO_ACCOUNT_DETAILS);
builder->Add("fatalErrorMessageNoPassword",
IDS_LOGIN_FATAL_ERROR_NO_PASSWORD);
builder->Add("fatalErrorMessageVerificationFailed",
IDS_LOGIN_FATAL_ERROR_PASSWORD_VERIFICATION);
builder->Add("fatalErrorMessageInsecureURL",
IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL);
builder->Add("fatalErrorDoneButton", IDS_DONE);
builder->Add("fatalErrorTryAgainButton",
IDS_LOGIN_FATAL_ERROR_TRY_AGAIN_BUTTON);
builder->AddF("loginWelcomeMessage", IDS_LOGIN_WELCOME_MESSAGE,
ui::GetChromeOSDeviceTypeResourceId());
builder->Add("offlineLoginEmail", IDS_OFFLINE_LOGIN_EMAIL);
builder->Add("offlineLoginPassword", IDS_OFFLINE_LOGIN_PASSWORD);
builder->Add("offlineLoginInvalidEmail", IDS_OFFLINE_LOGIN_INVALID_EMAIL);
builder->Add("offlineLoginInvalidPassword",
IDS_OFFLINE_LOGIN_INVALID_PASSWORD);
builder->Add("offlineLoginNextBtn", IDS_OFFLINE_LOGIN_NEXT_BUTTON_TEXT);
builder->Add("offlineLoginForgotPasswordBtn",
IDS_OFFLINE_LOGIN_FORGOT_PASSWORD_BUTTON_TEXT);
builder->Add("offlineLoginForgotPasswordDlg",
IDS_OFFLINE_LOGIN_FORGOT_PASSWORD_DIALOG_TEXT);
builder->Add("offlineLoginCloseBtn", IDS_OFFLINE_LOGIN_CLOSE_BUTTON_TEXT);
builder->Add("enterpriseInfoMessage", IDS_LOGIN_DEVICE_MANAGED_BY_NOTICE);
builder->Add("samlInterstitialMessage",
IDS_LOGIN_SAML_INTERSTITIAL_MESSAGE);
builder->Add("samlInterstitialChangeAccountLink",
IDS_LOGIN_SAML_INTERSTITIAL_CHANGE_ACCOUNT_LINK_TEXT);
builder->Add("samlInterstitialNextBtn",
IDS_LOGIN_SAML_INTERSTITIAL_NEXT_BUTTON_TEXT);
builder->Add("adAuthWelcomeMessage", IDS_AD_DOMAIN_AUTH_WELCOME_MESSAGE);
builder->Add("adAuthLoginUsername", IDS_AD_AUTH_LOGIN_USER);
builder->Add("adLoginPassword", IDS_AD_LOGIN_PASSWORD);
builder->Add("adPassChangeOldPasswordHint",
IDS_AD_PASSWORD_CHANGE_OLD_PASSWORD_HINT);
builder->Add("adPassChangeNewPasswordHint",
IDS_AD_PASSWORD_CHANGE_NEW_PASSWORD_HINT);
builder->Add("adPassChangeRepeatNewPasswordHint",
IDS_AD_PASSWORD_CHANGE_REPEAT_NEW_PASSWORD_HINT);
builder->Add("adPassChangeOldPasswordError",
IDS_AD_PASSWORD_CHANGE_INVALID_PASSWORD_ERROR);
builder->Add("adPassChangeNewPasswordRejected",
IDS_AD_PASSWORD_CHANGE_NEW_PASSWORD_REJECTED_SHORT_ERROR);
builder->Add("adPassChangePasswordsMismatch",
IDS_AD_PASSWORD_CHANGE_PASSWORDS_MISMATCH_ERROR);
}
void GaiaScreenHandler::Initialize() {
}
void GaiaScreenHandler::RegisterMessages() {
AddCallback("webviewLoadAborted",
&GaiaScreenHandler::HandleWebviewLoadAborted);
AddCallback("completeLogin", &GaiaScreenHandler::HandleCompleteLogin);
AddCallback("completeAuthentication",
&GaiaScreenHandler::HandleCompleteAuthentication);
AddCallback("usingSAMLAPI", &GaiaScreenHandler::HandleUsingSAMLAPI);
AddCallback("scrapedPasswordCount",
&GaiaScreenHandler::HandleScrapedPasswordCount);
AddCallback("scrapedPasswordVerificationFailed",
&GaiaScreenHandler::HandleScrapedPasswordVerificationFailed);
AddCallback("loginWebuiReady", &GaiaScreenHandler::HandleGaiaUIReady);
AddCallback("identifierEntered", &GaiaScreenHandler::HandleIdentifierEntered);
AddCallback("updateOfflineLogin",
&GaiaScreenHandler::set_offline_login_is_active);
AddCallback("authExtensionLoaded",
&GaiaScreenHandler::HandleAuthExtensionLoaded);
AddCallback("completeAdAuthentication",
&GaiaScreenHandler::HandleCompleteAdAuthentication);
AddCallback("cancelAdAuthentication",
&GaiaScreenHandler::HandleCancelActiveDirectoryAuth);
AddRawCallback("showAddUser", &GaiaScreenHandler::HandleShowAddUser);
AddCallback("getIsSamlUserPasswordless",
&GaiaScreenHandler::HandleGetIsSamlUserPasswordless);
AddCallback("updateOobeDialogSize",
&GaiaScreenHandler::HandleUpdateOobeDialogSize);
AddCallback("hideOobeDialog", &GaiaScreenHandler::HandleHideOobeDialog);
// Allow UMA metrics collection from JS.
web_ui()->AddMessageHandler(std::make_unique<MetricsHandler>());
}
void GaiaScreenHandler::OnPortalDetectionCompleted(
const NetworkState* network,
const NetworkPortalDetector::CaptivePortalState& state) {
VLOG(1) << "OnPortalDetectionCompleted "
<< NetworkPortalDetector::CaptivePortalStatusString(state.status);
const NetworkPortalDetector::CaptivePortalStatus previous_status =
captive_portal_status_;
captive_portal_status_ = state.status;
if (offline_login_is_active() ||
IsOnline(captive_portal_status_) == IsOnline(previous_status) ||
disable_restrictive_proxy_check_for_test_ ||
GetCurrentScreen() != kScreenId)
return;
LoadAuthExtension(true /* force */, false /* offline */);
}
void GaiaScreenHandler::HandleIdentifierEntered(const std::string& user_email) {
if (LoginDisplayHost::default_host() &&
!LoginDisplayHost::default_host()->IsUserWhitelisted(
user_manager::known_user::GetAccountId(
user_email, std::string() /* id */, AccountType::UNKNOWN))) {
ShowWhitelistCheckFailedError();
}
}
void GaiaScreenHandler::HandleAuthExtensionLoaded() {
VLOG(1) << "Auth extension finished loading";
auth_extension_being_loaded_ = false;
}
void GaiaScreenHandler::HandleWebviewLoadAborted(
const std::string& error_reason_str) {
// TODO(nkostylev): Switch to int code once webview supports that.
// http://crbug.com/470483
if (error_reason_str == "ERR_ABORTED") {
LOG(WARNING) << "Ignoring Gaia webview error: " << error_reason_str;
return;
}
// TODO(nkostylev): Switch to int code once webview supports that.
// http://crbug.com/470483
// Extract some common codes used by SigninScreenHandler for now.
if (error_reason_str == "ERR_NAME_NOT_RESOLVED")
frame_error_ = net::ERR_NAME_NOT_RESOLVED;
else if (error_reason_str == "ERR_INTERNET_DISCONNECTED")
frame_error_ = net::ERR_INTERNET_DISCONNECTED;
else if (error_reason_str == "ERR_NETWORK_CHANGED")
frame_error_ = net::ERR_NETWORK_CHANGED;
else if (error_reason_str == "ERR_INTERNET_DISCONNECTED")
frame_error_ = net::ERR_INTERNET_DISCONNECTED;
else if (error_reason_str == "ERR_PROXY_CONNECTION_FAILED")
frame_error_ = net::ERR_PROXY_CONNECTION_FAILED;
else if (error_reason_str == "ERR_TUNNEL_CONNECTION_FAILED")
frame_error_ = net::ERR_TUNNEL_CONNECTION_FAILED;
else
frame_error_ = net::ERR_INTERNET_DISCONNECTED;
LOG(ERROR) << "Gaia webview error: " << error_reason_str;
NetworkError::ErrorReason error_reason =
NetworkError::ERROR_REASON_FRAME_ERROR;
frame_state_ = FRAME_STATE_ERROR;
UpdateState(error_reason);
}
AccountId GaiaScreenHandler::GetAccountId(
const std::string& authenticated_email,
const std::string& id,
const AccountType& account_type) const {
const std::string canonicalized_email =
gaia::CanonicalizeEmail(gaia::SanitizeEmail(authenticated_email));
const AccountId account_id = user_manager::known_user::GetAccountId(
authenticated_email, id, account_type);
if (account_id.GetUserEmail() != canonicalized_email) {
LOG(WARNING) << "Existing user '" << account_id.GetUserEmail()
<< "' authenticated by alias '" << canonicalized_email << "'.";
}
return account_id;
}
void GaiaScreenHandler::DoAdAuth(
const std::string& username,
const Key& key,
authpolicy::ErrorType error,
const authpolicy::ActiveDirectoryAccountInfo& account_info) {
if (error != authpolicy::ERROR_NONE)
authpolicy_login_helper_->CancelRequestsAndRestart();
switch (error) {
case authpolicy::ERROR_NONE: {
DCHECK(account_info.has_account_id() &&
!account_info.account_id().empty() &&
LoginDisplayHost::default_host());
const AccountId account_id(GetAccountId(
username, account_info.account_id(), AccountType::ACTIVE_DIRECTORY));
LoginDisplayHost::default_host()->SetDisplayAndGivenName(
account_info.display_name(), account_info.given_name());
UserContext user_context(
user_manager::UserType::USER_TYPE_ACTIVE_DIRECTORY, account_id);
user_context.SetKey(key);
user_context.SetAuthFlow(UserContext::AUTH_FLOW_ACTIVE_DIRECTORY);
user_context.SetIsUsingOAuth(false);
LoginDisplayHost::default_host()->CompleteLogin(user_context);
break;
}
case authpolicy::ERROR_PASSWORD_EXPIRED:
DCHECK(active_directory_password_change_screen_handler_);
active_directory_password_change_screen_handler_->ShowScreen(username);
break;
case authpolicy::ERROR_PARSE_UPN_FAILED:
case authpolicy::ERROR_BAD_USER_NAME:
CallJS("invalidateAd", username,
static_cast<int>(ActiveDirectoryErrorState::BAD_USERNAME));
break;
case authpolicy::ERROR_BAD_PASSWORD:
CallJS("invalidateAd", username,
static_cast<int>(ActiveDirectoryErrorState::BAD_AUTH_PASSWORD));
break;
default:
CallJS("invalidateAd", username,
static_cast<int>(ActiveDirectoryErrorState::NONE));
core_oobe_view_->ShowSignInError(
0, GetAdErrorMessage(error), std::string(),
HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
}
}
void GaiaScreenHandler::HandleCompleteAdAuthentication(
const std::string& username,
const std::string& password) {
if (LoginDisplayHost::default_host())
LoginDisplayHost::default_host()->SetDisplayEmail(username);
set_populated_email(username);
DCHECK(authpolicy_login_helper_);
authpolicy_login_helper_->AuthenticateUser(
username, std::string() /* object_guid */, password,
base::BindOnce(&GaiaScreenHandler::DoAdAuth, weak_factory_.GetWeakPtr(),
username, Key(password)));
}
void GaiaScreenHandler::HandleCancelActiveDirectoryAuth() {
DCHECK(authpolicy_login_helper_);
authpolicy_login_helper_->CancelRequestsAndRestart();
}
void GaiaScreenHandler::HandleCompleteAuthentication(
const std::string& gaia_id,
const std::string& email,
const std::string& password,
const std::string& auth_code,
bool using_saml,
const std::string& gaps_cookie,
const ::login::StringList& services) {
if (!LoginDisplayHost::default_host())
return;
DCHECK(!email.empty());
DCHECK(!gaia_id.empty());
const std::string sanitized_email = gaia::SanitizeEmail(email);
LoginDisplayHost::default_host()->SetDisplayEmail(sanitized_email);
const user_manager::UserType user_type =
GetUsertypeFromServicesString(services);
UserContext user_context(user_type,
GetAccountId(email, gaia_id, AccountType::GOOGLE));
user_context.SetKey(Key(password));
// Save the user's plaintext password for possible authentication to a
// network. See https://crbug.com/386606 for details.
user_context.SetPasswordKey(Key(password));
user_context.SetAuthCode(auth_code);
user_context.SetAuthFlow(using_saml
? UserContext::AUTH_FLOW_GAIA_WITH_SAML
: UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
user_context.SetGAPSCookie(gaps_cookie);
LoginDisplayHost::default_host()->CompleteLogin(user_context);
}
void GaiaScreenHandler::HandleCompleteLogin(const std::string& gaia_id,
const std::string& typed_email,
const std::string& password,
bool using_saml) {
VLOG(1) << "HandleCompleteLogin";
DoCompleteLogin(gaia_id, typed_email, password, using_saml);
}
void GaiaScreenHandler::HandleUsingSAMLAPI() {
SetSAMLPrincipalsAPIUsed(true);
}
void GaiaScreenHandler::HandleScrapedPasswordCount(int password_count) {
SetSAMLPrincipalsAPIUsed(false);
// Use a histogram that has 11 buckets, one for each of the values in [0, 9]
// and an overflow bucket at the end.
UMA_HISTOGRAM_ENUMERATION(
"ChromeOS.SAML.Scraping.PasswordCount", std::min(password_count, 10), 11);
if (password_count == 0)
HandleScrapedPasswordVerificationFailed();
}
void GaiaScreenHandler::HandleScrapedPasswordVerificationFailed() {
RecordSAMLScrapingVerificationResultInHistogram(false);
}
void GaiaScreenHandler::HandleGaiaUIReady() {
VLOG(1) << "Gaia is loaded";
// As we could miss and window.onload could already be called, restore
// focus to current pod (see crbug/175243).
if (gaia_silent_load_)
signin_screen_handler_->RefocusCurrentPod();
frame_error_ = net::OK;
frame_state_ = FRAME_STATE_LOADED;
if (network_state_informer_->state() == NetworkStateInformer::ONLINE)
UpdateState(NetworkError::ERROR_REASON_UPDATE);
if (test_expects_complete_login_)
SubmitLoginFormForTest();
if (LoginDisplayHost::default_host())
LoginDisplayHost::default_host()->OnGaiaScreenReady();
}
void GaiaScreenHandler::HandleUpdateOobeDialogSize(int width, int height) {
if (LoginDisplayHost::default_host())
LoginDisplayHost::default_host()->UpdateOobeDialogSize(width, height);
}
void GaiaScreenHandler::HandleHideOobeDialog() {
if (LoginDisplayHost::default_host())
LoginDisplayHost::default_host()->HideOobeDialog();
}
void GaiaScreenHandler::HandleShowAddUser(const base::ListValue* args) {
// TODO(xiaoyinh): Add trace event for gaia webui in views login screen.
TRACE_EVENT_ASYNC_STEP_INTO0("ui", "ShowLoginWebUI",
LoginDisplayHostWebUI::kShowLoginWebUIid,
"ShowAddUser");
std::string email;
// |args| can be null if it's OOBE.
if (args)
args->GetString(0, &email);
set_populated_email(email);
if (!email.empty())
SendReauthReason(AccountId::FromUserEmail(email));
OnShowAddUser();
}
void GaiaScreenHandler::HandleGetIsSamlUserPasswordless(
const std::string& callback_id,
const std::string& typed_email,
const std::string& gaia_id) {
AllowJavascript();
// TODO(emaxx,https://crbug.com/826417): Determine the result value based on
// known_user properties if the user already existed, or the
// DeviceSamlLoginAuthenticationType policy if that's a new user.
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(false) /* isSamlUserPasswordless */);
}
void GaiaScreenHandler::OnShowAddUser() {
signin_screen_handler_->is_account_picker_showing_first_time_ = false;
lock_screen_utils::EnforcePolicyInputMethods(std::string());
ShowGaiaAsync(base::nullopt);
}
void GaiaScreenHandler::DoCompleteLogin(const std::string& gaia_id,
const std::string& typed_email,
const std::string& password,
bool using_saml) {
if (!LoginDisplayHost::default_host())
return;
if (using_saml && !using_saml_api_)
RecordSAMLScrapingVerificationResultInHistogram(true);
DCHECK(!typed_email.empty());
DCHECK(!gaia_id.empty());
const std::string sanitized_email = gaia::SanitizeEmail(typed_email);
LoginDisplayHost::default_host()->SetDisplayEmail(sanitized_email);
const AccountId account_id =
GetAccountId(typed_email, gaia_id, AccountType::GOOGLE);
const user_manager::User* const user =
user_manager::UserManager::Get()->FindUser(account_id);
UserContext user_context =
user ? UserContext(*user)
: UserContext(CalculateUserType(account_id), account_id);
user_context.SetKey(Key(password));
// Save the user's plaintext password for possible authentication to a
// network. If the user's OpenNetworkConfiguration policy contains a
// ${PASSWORD} variable, then the user's password will be used to authenticate
// to the specified network.
//
// The user's password needs to be saved in memory until the policy can be
// examined. When the policy comes in, if it does not contain the ${PASSWORD}
// variable, the user's password will be discarded. If it contains the
// password, it will be sent to the session manager, which will then save it
// in a keyring so it can be retrieved for authenticating to the network.
//
// More details can be found in https://crbug.com/386606
user_context.SetPasswordKey(Key(password));
user_context.SetAuthFlow(using_saml
? UserContext::AUTH_FLOW_GAIA_WITH_SAML
: UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
LoginDisplayHost::default_host()->CompleteLogin(user_context);
if (test_expects_complete_login_) {
VLOG(2) << "Complete test login for " << typed_email
<< ", requested=" << test_user_;
test_expects_complete_login_ = false;
test_user_.clear();
test_pass_.clear();
}
}
void GaiaScreenHandler::StartClearingDnsCache() {
if (dns_clear_task_running_ ||
!g_browser_process->system_network_context_manager()) {
return;
}
dns_cleared_ = false;
g_browser_process->system_network_context_manager()
->GetContext()
->ClearHostCache(nullptr /* filter */,
// Need to ensure that even if the network service
// crashes, OnDnsCleared() is invoked.
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&GaiaScreenHandler::OnDnsCleared,
weak_factory_.GetWeakPtr()),
base::BindOnce(&GaiaScreenHandler::OnDnsCleared,
weak_factory_.GetWeakPtr())));
dns_clear_task_running_ = true;
}
void GaiaScreenHandler::OnDnsCleared() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
dns_clear_task_running_ = false;
dns_cleared_ = true;
ShowGaiaScreenIfReady();
}
void GaiaScreenHandler::StartClearingCookies(
const base::Closure& on_clear_callback) {
cookies_cleared_ = false;
ProfileHelper* profile_helper = ProfileHelper::Get();
LOG_ASSERT(Profile::FromWebUI(web_ui()) ==
profile_helper->GetSigninProfile());
profile_helper->ClearSigninProfile(
base::Bind(&GaiaScreenHandler::OnCookiesCleared,
weak_factory_.GetWeakPtr(), on_clear_callback));
}
void GaiaScreenHandler::OnCookiesCleared(
const base::Closure& on_clear_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
cookies_cleared_ = true;
on_clear_callback.Run();
}
void GaiaScreenHandler::SubmitLoginFormForTest() {
VLOG(2) << "Submit login form for test, user=" << test_user_;
content::RenderFrameHost* frame =
signin::GetAuthFrame(web_ui()->GetWebContents(), kAuthIframeParentName);
std::string code =
"document.getElementById('identifier').value = '" + test_user_ + "';"
"document.getElementById('nextButton').click();";
frame->ExecuteJavaScriptForTests(base::ASCIIToUTF16(code));
if (!test_services_.empty()) {
// Prefix each doublequote with backslash, so that it will remain correct
// JSON after assigning to the element property.
std::string escaped_services;
base::ReplaceChars(test_services_, "\"", "\\\"", &escaped_services);
code = "document.getElementById('services').value = \"" + escaped_services +
"\";";
frame->ExecuteJavaScriptForTests(base::ASCIIToUTF16(code));
}
if (!test_pass_.empty()) {
code = "document.getElementById('password').value = '" + test_pass_ + "';";
code += "document.getElementById('nextButton').click();";
frame->ExecuteJavaScriptForTests(base::ASCIIToUTF16(code));
}
// Test properties are cleared in HandleCompleteLogin because the form
// submission might fail and login will not be attempted after reloading
// if they are cleared here.
}
void GaiaScreenHandler::SetSAMLPrincipalsAPIUsed(bool api_used) {
using_saml_api_ = api_used;
UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.APIUsed", api_used);
}
void GaiaScreenHandler::ShowGaiaAsync(
const base::Optional<AccountId>& account_id) {
if (account_id)
populated_email_ = account_id->GetUserEmail();
show_when_dns_and_cookies_cleared_ = true;
if (gaia_silent_load_ && populated_email_.empty()) {
dns_cleared_ = true;
cookies_cleared_ = true;
ShowGaiaScreenIfReady();
} else {
StartClearingDnsCache();
StartClearingCookies(base::Bind(&GaiaScreenHandler::ShowGaiaScreenIfReady,
weak_factory_.GetWeakPtr()));
}
}
void GaiaScreenHandler::ShowSigninScreenForTest(const std::string& username,
const std::string& password,
const std::string& services) {
VLOG(2) << "ShowSigninScreenForTest for user " << username
<< ", frame_state=" << frame_state();
test_user_ = username;
test_pass_ = password;
test_services_ = services;
test_expects_complete_login_ = true;
// Submit login form for test if gaia is ready. If gaia is loading, login
// will be attempted in HandleLoginWebuiReady after gaia is ready. Otherwise,
// reload gaia then follow the loading case.
if (frame_state() == GaiaScreenHandler::FRAME_STATE_LOADED) {
SubmitLoginFormForTest();
} else if (frame_state() != GaiaScreenHandler::FRAME_STATE_LOADING) {
OnShowAddUser();
}
}
void GaiaScreenHandler::CancelShowGaiaAsync() {
show_when_dns_and_cookies_cleared_ = false;
}
void GaiaScreenHandler::ShowGaiaScreenIfReady() {
if (!dns_cleared_ || !cookies_cleared_ ||
!show_when_dns_and_cookies_cleared_ ||
!LoginDisplayHost::default_host()) {
return;
}
std::string active_network_path = network_state_informer_->network_path();
if (gaia_silent_load_ &&
(network_state_informer_->state() != NetworkStateInformer::ONLINE ||
gaia_silent_load_network_ != active_network_path)) {
// Network has changed. Force Gaia reload.
gaia_silent_load_ = false;
}
// Views-based login may reach here while pre-loading the Gaia screen, so
// update the wallpaper in |LoginDisplayHostMojo::UpdateGaiaDialogVisibility|
// instead, which controls the actual visibility of the Gaia screen.
if (!ash::features::IsViewsLoginEnabled()) {
// Note that LoadAuthExtension clears |populated_email_|.
if (populated_email_.empty()) {
LoginDisplayHost::default_host()->LoadSigninWallpaper();
} else {
LoginDisplayHost::default_host()->LoadWallpaper(
user_manager::known_user::GetAccountId(
populated_email_, std::string() /* id */, AccountType::UNKNOWN));
}
}
input_method::InputMethodManager* imm =
input_method::InputMethodManager::Get();
scoped_refptr<input_method::InputMethodManager::State> gaia_ime_state =
imm->GetActiveIMEState()->Clone();
imm->SetState(gaia_ime_state);
// Set Least Recently Used input method for the user.
if (!populated_email_.empty()) {
lock_screen_utils::SetUserInputMethod(populated_email_,
gaia_ime_state.get());
} else {
std::vector<std::string> input_methods;
if (gaia_ime_state->GetAllowedInputMethods().empty()) {
input_methods =
imm->GetInputMethodUtil()->GetHardwareLoginInputMethodIds();
} else {
input_methods = gaia_ime_state->GetAllowedInputMethods();
}
const std::string owner_im = lock_screen_utils::GetUserLastInputMethod(
user_manager::UserManager::Get()->GetOwnerAccountId().GetUserEmail());
const std::string system_im = g_browser_process->local_state()->GetString(
language_prefs::kPreferredKeyboardLayout);
PushFrontIMIfNotExists(owner_im, &input_methods);
PushFrontIMIfNotExists(system_im, &input_methods);
gaia_ime_state->EnableLoginLayouts(
g_browser_process->GetApplicationLocale(), input_methods);
if (!system_im.empty()) {
gaia_ime_state->ChangeInputMethod(system_im, false /* show_message */);
} else if (!owner_im.empty()) {
gaia_ime_state->ChangeInputMethod(owner_im, false /* show_message */);
}
}
if (!untrusted_authority_certs_cache_) {
// Make additional untrusted authority certificates available for client
// certificate discovery in case a SAML flow is used which requires a client
// certificate to be present.
// When the WebUI is destroyed, |untrusted_authority_certs_cache_| will go
// out of scope and the certificates will not be held in memory anymore.
untrusted_authority_certs_cache_ =
std::make_unique<policy::TempCertsCacheNSS>(
policy::TempCertsCacheNSS::
GetUntrustedAuthoritiesFromDeviceOncPolicy());
}
LoadAuthExtension(!gaia_silent_load_ /* force */, false /* offline */);
signin_screen_handler_->UpdateUIState(
SigninScreenHandler::UI_STATE_GAIA_SIGNIN, nullptr);
core_oobe_view_->UpdateKeyboardState();
if (gaia_silent_load_) {
// The variable is assigned to false because silently loaded Gaia page was
// used.
gaia_silent_load_ = false;
}
UpdateState(NetworkError::ERROR_REASON_UPDATE);
if (core_oobe_view_) {
PrefService* prefs = g_browser_process->local_state();
if (prefs->GetBoolean(prefs::kFactoryResetRequested)) {
core_oobe_view_->ShowDeviceResetScreen();
} else if (prefs->GetBoolean(prefs::kDebuggingFeaturesRequested)) {
core_oobe_view_->ShowEnableDebuggingScreen();
}
}
}
void GaiaScreenHandler::ShowWhitelistCheckFailedError() {
base::DictionaryValue params;
params.SetBoolean("enterpriseManaged",
g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->IsEnterpriseManaged());
CallJS("showWhitelistCheckFailedError", true, params);
}
void GaiaScreenHandler::LoadAuthExtension(bool force,
bool offline) {
VLOG(1) << "LoadAuthExtension, force: " << force
<< ", offline: " << offline;
if (auth_extension_being_loaded_) {
VLOG(1) << "Skip loading the Auth extension as it's already being loaded";
return;
}
auth_extension_being_loaded_ = true;
GaiaContext context;
context.force_reload = force;
context.use_offline = offline;
context.email = populated_email_;
std::string gaia_id;
if (!context.email.empty() &&
user_manager::known_user::FindGaiaID(
AccountId::FromUserEmail(context.email), &gaia_id)) {
context.gaia_id = gaia_id;
}
if (!context.email.empty()) {
context.gaps_cookie = user_manager::known_user::GetGAPSCookie(
AccountId::FromUserEmail(gaia::CanonicalizeEmail(context.email)));
}
populated_email_.clear();
LoadGaia(context);
}
void GaiaScreenHandler::UpdateState(NetworkError::ErrorReason reason) {
if (signin_screen_handler_)
signin_screen_handler_->UpdateState(reason);
}
bool GaiaScreenHandler::IsRestrictiveProxy() const {
return !disable_restrictive_proxy_check_for_test_ &&
!IsOnline(captive_portal_status_);
}
} // namespace chromeos