// 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/options/create_profile_handler.h" #include "base/bind.h" #include "base/files/file_path.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_service.h" #include "base/strings/string_util.h" #include "base/value_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_metrics.h" #include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/ui/webui/options/options_handlers_helper.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/web_ui.h" #include "ui/base/l10n/l10n_util.h" #if defined(ENABLE_SUPERVISED_USERS) #include "chrome/browser/supervised_user/legacy/supervised_user_registration_utility.h" #include "chrome/browser/supervised_user/legacy/supervised_user_sync_service.h" #include "chrome/browser/supervised_user/legacy/supervised_user_sync_service_factory.h" #include "chrome/browser/supervised_user/supervised_user_service.h" #include "chrome/browser/supervised_user/supervised_user_service_factory.h" #endif namespace options { CreateProfileHandler::CreateProfileHandler() : profile_creation_type_(NO_CREATION_IN_PROGRESS), weak_ptr_factory_(this) { } CreateProfileHandler::~CreateProfileHandler() { #if defined(ENABLE_SUPERVISED_USERS) // Cancellation is only supported for supervised users. CancelProfileRegistration(false); #endif } void CreateProfileHandler::GetLocalizedValues( base::DictionaryValue* localized_strings) { } void CreateProfileHandler::RegisterMessages() { #if defined(ENABLE_SUPERVISED_USERS) // Cancellation is only supported for supervised users. web_ui()->RegisterMessageCallback( "cancelCreateProfile", base::Bind(&CreateProfileHandler::HandleCancelProfileCreation, base::Unretained(this))); #endif web_ui()->RegisterMessageCallback( "createProfile", base::Bind(&CreateProfileHandler::CreateProfile, base::Unretained(this))); } void CreateProfileHandler::CreateProfile(const base::ListValue* args) { #if defined(ENABLE_SUPERVISED_USERS) // This handler could have been called for a supervised user, for example // because the user fiddled with the web inspector. Silently return. if (Profile::FromWebUI(web_ui())->IsSupervised()) return; #endif if (!profiles::IsMultipleProfilesEnabled()) return; // We can have only one in progress profile creation // at any given moment, if new ones are initiated just // ignore them until we are done with the old one. if (profile_creation_type_ != NO_CREATION_IN_PROGRESS) return; profile_creation_type_ = NON_SUPERVISED_PROFILE_CREATION; DCHECK(profile_path_being_created_.empty()); profile_creation_start_time_ = base::TimeTicks::Now(); base::string16 name; base::string16 icon; bool create_shortcut = false; if (args->GetString(0, &name) && args->GetString(1, &icon)) { base::TrimWhitespace(name, base::TRIM_ALL, &name); CHECK(!name.empty()); args->GetBoolean(2, &create_shortcut); } std::string supervised_user_id; #if defined(ENABLE_SUPERVISED_USERS) if (!ProcessSupervisedCreateProfileArgs(args, &supervised_user_id)) return; #endif ProfileMetrics::LogProfileAddNewUser(ProfileMetrics::ADD_NEW_USER_DIALOG); profile_path_being_created_ = ProfileManager::CreateMultiProfileAsync( name, icon, base::Bind(&CreateProfileHandler::OnProfileCreated, weak_ptr_factory_.GetWeakPtr(), create_shortcut, helper::GetDesktopType(web_ui()), supervised_user_id), supervised_user_id); } void CreateProfileHandler::OnProfileCreated( bool create_shortcut, chrome::HostDesktopType desktop_type, const std::string& supervised_user_id, Profile* profile, Profile::CreateStatus status) { if (status != Profile::CREATE_STATUS_CREATED) RecordProfileCreationMetrics(status); switch (status) { case Profile::CREATE_STATUS_LOCAL_FAIL: { ShowProfileCreationError(profile, GetProfileCreationErrorMessageLocal()); break; } case Profile::CREATE_STATUS_CREATED: { // Do nothing for an intermediate status. break; } case Profile::CREATE_STATUS_INITIALIZED: { HandleProfileCreationSuccess(create_shortcut, desktop_type, supervised_user_id, profile); break; } // User-initiated cancellation is handled in CancelProfileRegistration and // does not call this callback. case Profile::CREATE_STATUS_CANCELED: // Supervised user registration errors are handled in // OnSupervisedUserRegistered(). case Profile::CREATE_STATUS_REMOTE_FAIL: case Profile::MAX_CREATE_STATUS: { NOTREACHED(); break; } } } void CreateProfileHandler::HandleProfileCreationSuccess( bool create_shortcut, chrome::HostDesktopType desktop_type, const std::string& supervised_user_id, Profile* profile) { switch (profile_creation_type_) { case NON_SUPERVISED_PROFILE_CREATION: { DCHECK(supervised_user_id.empty()); CreateShortcutAndShowSuccess(create_shortcut, desktop_type, profile); break; } #if defined(ENABLE_SUPERVISED_USERS) case SUPERVISED_PROFILE_CREATION: case SUPERVISED_PROFILE_IMPORT: RegisterSupervisedUser(create_shortcut, desktop_type, supervised_user_id, profile); break; #endif case NO_CREATION_IN_PROGRESS: NOTREACHED(); break; } } void CreateProfileHandler::CreateShortcutAndShowSuccess( bool create_shortcut, chrome::HostDesktopType desktop_type, Profile* profile) { if (create_shortcut) { ProfileShortcutManager* shortcut_manager = g_browser_process->profile_manager()->profile_shortcut_manager(); if (shortcut_manager) shortcut_manager->CreateProfileShortcut(profile->GetPath()); } DCHECK_EQ(profile_path_being_created_.value(), profile->GetPath().value()); profile_path_being_created_.clear(); DCHECK_NE(NO_CREATION_IN_PROGRESS, profile_creation_type_); base::DictionaryValue dict; dict.SetString("name", profile->GetPrefs()->GetString(prefs::kProfileName)); dict.Set("filePath", base::CreateFilePathValue(profile->GetPath())); #if defined(ENABLE_SUPERVISED_USERS) bool is_supervised = profile_creation_type_ == SUPERVISED_PROFILE_CREATION || profile_creation_type_ == SUPERVISED_PROFILE_IMPORT; dict.SetBoolean("isSupervised", is_supervised); #endif web_ui()->CallJavascriptFunction( GetJavascriptMethodName(PROFILE_CREATION_SUCCESS), dict); // If the new profile is a supervised user, instead of opening a new window // right away, a confirmation overlay will be shown by JS from the creation // dialog. If we are importing an existing supervised profile or creating a // new non-supervised user profile we don't show any confirmation, so open // the new window now. bool should_open_new_window = true; #if defined(ENABLE_SUPERVISED_USERS) if (profile_creation_type_ == SUPERVISED_PROFILE_CREATION) should_open_new_window = false; #endif if (should_open_new_window) { // Opening the new window must be the last action, after all callbacks // have been run, to give them a chance to initialize the profile. helper::OpenNewWindowForProfile(desktop_type, profile, Profile::CREATE_STATUS_INITIALIZED); } profile_creation_type_ = NO_CREATION_IN_PROGRESS; } void CreateProfileHandler::ShowProfileCreationError( Profile* profile, const base::string16& error) { DCHECK_NE(NO_CREATION_IN_PROGRESS, profile_creation_type_); profile_creation_type_ = NO_CREATION_IN_PROGRESS; profile_path_being_created_.clear(); web_ui()->CallJavascriptFunction( GetJavascriptMethodName(PROFILE_CREATION_ERROR), base::StringValue(error)); // The ProfileManager calls us back with a NULL profile in some cases. if (profile) helper::DeleteProfileAtPath(profile->GetPath(), web_ui()); } void CreateProfileHandler::RecordProfileCreationMetrics( Profile::CreateStatus status) { UMA_HISTOGRAM_ENUMERATION("Profile.CreateResult", status, Profile::MAX_CREATE_STATUS); UMA_HISTOGRAM_MEDIUM_TIMES( "Profile.CreateTimeNoTimeout", base::TimeTicks::Now() - profile_creation_start_time_); } base::string16 CreateProfileHandler::GetProfileCreationErrorMessageLocal() const { int message_id = IDS_PROFILES_CREATE_LOCAL_ERROR; #if defined(ENABLE_SUPERVISED_USERS) // Local errors can occur during supervised profile import. if (profile_creation_type_ == SUPERVISED_PROFILE_IMPORT) message_id = IDS_SUPERVISED_USER_IMPORT_LOCAL_ERROR; #endif return l10n_util::GetStringUTF16(message_id); } #if defined(ENABLE_SUPERVISED_USERS) base::string16 CreateProfileHandler::GetProfileCreationErrorMessageRemote() const { return l10n_util::GetStringUTF16( profile_creation_type_ == SUPERVISED_PROFILE_IMPORT ? IDS_SUPERVISED_USER_IMPORT_REMOTE_ERROR : IDS_PROFILES_CREATE_REMOTE_ERROR); } base::string16 CreateProfileHandler::GetProfileCreationErrorMessageSignin() const { return l10n_util::GetStringUTF16( profile_creation_type_ == SUPERVISED_PROFILE_IMPORT ? IDS_SUPERVISED_USER_IMPORT_SIGN_IN_ERROR : IDS_PROFILES_CREATE_SIGN_IN_ERROR); } #endif std::string CreateProfileHandler::GetJavascriptMethodName( ProfileCreationStatus status) const { switch (profile_creation_type_) { #if defined(ENABLE_SUPERVISED_USERS) case SUPERVISED_PROFILE_IMPORT: switch (status) { case PROFILE_CREATION_SUCCESS: return "BrowserOptions.showSupervisedUserImportSuccess"; case PROFILE_CREATION_ERROR: return "BrowserOptions.showSupervisedUserImportError"; } break; #endif default: switch (status) { case PROFILE_CREATION_SUCCESS: return "BrowserOptions.showCreateProfileSuccess"; case PROFILE_CREATION_ERROR: return "BrowserOptions.showCreateProfileError"; } break; } NOTREACHED(); return std::string(); } #if defined(ENABLE_SUPERVISED_USERS) bool CreateProfileHandler::ProcessSupervisedCreateProfileArgs( const base::ListValue* args, std::string* supervised_user_id) { bool supervised_user = false; if (args->GetSize() >= 5) { bool success = args->GetBoolean(3, &supervised_user); DCHECK(success); success = args->GetString(4, supervised_user_id); DCHECK(success); } if (supervised_user) { if (!IsValidExistingSupervisedUserId(*supervised_user_id)) return false; profile_creation_type_ = SUPERVISED_PROFILE_IMPORT; if (supervised_user_id->empty()) { profile_creation_type_ = SUPERVISED_PROFILE_CREATION; *supervised_user_id = SupervisedUserRegistrationUtility::GenerateNewSupervisedUserId(); // If sync is not yet fully initialized, the creation may take extra time, // so show a message. Import doesn't wait for an acknowledgment, so it // won't have the same potential delay. ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetInstance()->GetForProfile( Profile::FromWebUI(web_ui())); ProfileSyncService::SyncStatusSummary status = sync_service->QuerySyncStatusSummary(); if (status == ProfileSyncService::DATATYPES_NOT_INITIALIZED) { ShowProfileCreationWarning(l10n_util::GetStringUTF16( IDS_PROFILES_CREATE_SUPERVISED_JUST_SIGNED_IN)); } } } return true; } void CreateProfileHandler::HandleCancelProfileCreation( const base::ListValue* args) { CancelProfileRegistration(true); } // Non-supervised user creation cannot be canceled. (Creating a non-supervised // profile shouldn't take significant time, and it can easily be deleted // afterward.) void CreateProfileHandler::CancelProfileRegistration(bool user_initiated) { if (profile_path_being_created_.empty()) return; ProfileManager* manager = g_browser_process->profile_manager(); Profile* new_profile = manager->GetProfileByPath(profile_path_being_created_); if (!new_profile || !new_profile->IsSupervised()) return; DCHECK(supervised_user_registration_utility_.get()); supervised_user_registration_utility_.reset(); if (user_initiated) { UMA_HISTOGRAM_MEDIUM_TIMES( "Profile.CreateTimeCanceledNoTimeout", base::TimeTicks::Now() - profile_creation_start_time_); RecordProfileCreationMetrics(Profile::CREATE_STATUS_CANCELED); } DCHECK_NE(NO_CREATION_IN_PROGRESS, profile_creation_type_); profile_creation_type_ = NO_CREATION_IN_PROGRESS; // Cancelling registration means the callback passed into // RegisterAndInitSync() won't be called, so the cleanup must be done here. profile_path_being_created_.clear(); helper::DeleteProfileAtPath(new_profile->GetPath(), web_ui()); } void CreateProfileHandler::RegisterSupervisedUser( bool create_shortcut, chrome::HostDesktopType desktop_type, const std::string& supervised_user_id, Profile* new_profile) { DCHECK_EQ(profile_path_being_created_.value(), new_profile->GetPath().value()); SupervisedUserService* supervised_user_service = SupervisedUserServiceFactory::GetForProfile(new_profile); // Register the supervised user using the profile of the custodian. supervised_user_registration_utility_ = SupervisedUserRegistrationUtility::Create(Profile::FromWebUI(web_ui())); supervised_user_service->RegisterAndInitSync( supervised_user_registration_utility_.get(), Profile::FromWebUI(web_ui()), supervised_user_id, base::Bind(&CreateProfileHandler::OnSupervisedUserRegistered, weak_ptr_factory_.GetWeakPtr(), create_shortcut, desktop_type, new_profile)); } void CreateProfileHandler::OnSupervisedUserRegistered( bool create_shortcut, chrome::HostDesktopType desktop_type, Profile* profile, const GoogleServiceAuthError& error) { GoogleServiceAuthError::State state = error.state(); RecordSupervisedProfileCreationMetrics(state); if (state == GoogleServiceAuthError::NONE) { CreateShortcutAndShowSuccess(create_shortcut, desktop_type, profile); return; } base::string16 error_msg; if (state == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS || state == GoogleServiceAuthError::USER_NOT_SIGNED_UP || state == GoogleServiceAuthError::ACCOUNT_DELETED || state == GoogleServiceAuthError::ACCOUNT_DISABLED) { error_msg = GetProfileCreationErrorMessageSignin(); } else { error_msg = GetProfileCreationErrorMessageRemote(); } ShowProfileCreationError(profile, error_msg); } void CreateProfileHandler::ShowProfileCreationWarning( const base::string16& warning) { DCHECK_EQ(SUPERVISED_PROFILE_CREATION, profile_creation_type_); web_ui()->CallJavascriptFunction("BrowserOptions.showCreateProfileWarning", base::StringValue(warning)); } void CreateProfileHandler::RecordSupervisedProfileCreationMetrics( GoogleServiceAuthError::State error_state) { if (profile_creation_type_ == SUPERVISED_PROFILE_CREATION) { UMA_HISTOGRAM_ENUMERATION("Profile.SupervisedProfileCreateError", error_state, GoogleServiceAuthError::NUM_STATES); UMA_HISTOGRAM_MEDIUM_TIMES( "Profile.SupervisedProfileTotalCreateTime", base::TimeTicks::Now() - profile_creation_start_time_); } else { DCHECK_EQ(SUPERVISED_PROFILE_IMPORT, profile_creation_type_); UMA_HISTOGRAM_ENUMERATION("Profile.SupervisedProfileImportError", error_state, GoogleServiceAuthError::NUM_STATES); UMA_HISTOGRAM_MEDIUM_TIMES( "Profile.SupervisedProfileTotalImportTime", base::TimeTicks::Now() - profile_creation_start_time_); } } bool CreateProfileHandler::IsValidExistingSupervisedUserId( const std::string& existing_supervised_user_id) const { if (existing_supervised_user_id.empty()) return true; Profile* profile = Profile::FromWebUI(web_ui()); const base::DictionaryValue* dict = SupervisedUserSyncServiceFactory::GetForProfile(profile)-> GetSupervisedUsers(); if (!dict->HasKey(existing_supervised_user_id)) return false; // Check if this supervised user already exists on this machine. const ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { if (existing_supervised_user_id == cache.GetSupervisedUserIdOfProfileAtIndex(i)) return false; } return true; } #endif } // namespace options