blob: f1769fbf839e81d1803e7c4c79e67f71efac0f48 [file] [log] [blame]
// Copyright (c) 2012 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/sync/sync_ui_util.h"
#include <utility>
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/url_util.h"
#if defined(OS_CHROMEOS)
#include "chromeos/constants/chromeos_features.h"
#endif
namespace sync_ui_util {
namespace {
StatusLabels GetStatusForUnrecoverableError(bool is_user_signout_allowed) {
#if !defined(OS_CHROMEOS)
int status_label_string_id =
is_user_signout_allowed
? IDS_SYNC_STATUS_UNRECOVERABLE_ERROR
:
// The message for managed accounts is the same as that on ChromeOS.
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT;
#else
int status_label_string_id =
IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT;
#endif
return {SYNC_ERROR, status_label_string_id, IDS_SYNC_RELOGIN_BUTTON,
REAUTHENTICATE};
}
// Depending on the authentication state, returns labels to be used to display
// information about the sync status.
StatusLabels GetStatusForAuthError(const GoogleServiceAuthError& auth_error) {
switch (auth_error.state()) {
case GoogleServiceAuthError::NONE:
NOTREACHED();
break;
case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
return {SYNC_ERROR, IDS_SYNC_SERVICE_UNAVAILABLE,
IDS_SETTINGS_EMPTY_STRING, NO_ACTION};
case GoogleServiceAuthError::CONNECTION_FAILED:
// Note that there is little the user can do if the server is not
// reachable. Since attempting to re-connect is done automatically by
// the Syncer, we do not show the (re)login link.
return {SYNC_ERROR, IDS_SYNC_SERVER_IS_UNREACHABLE,
IDS_SETTINGS_EMPTY_STRING, NO_ACTION};
case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
case GoogleServiceAuthError::SERVICE_ERROR:
default:
return {SYNC_ERROR, IDS_SYNC_RELOGIN_ERROR, IDS_SYNC_RELOGIN_BUTTON,
REAUTHENTICATE};
}
NOTREACHED();
return StatusLabels();
}
StatusLabels GetStatusLabelsImpl(const syncer::SyncService* service,
bool is_user_signout_allowed,
const GoogleServiceAuthError& auth_error) {
DCHECK(service);
if (!service->IsAuthenticatedAccountPrimary()) {
return {PRE_SYNCED, IDS_SETTINGS_EMPTY_STRING, IDS_SETTINGS_EMPTY_STRING,
NO_ACTION};
}
// If local Sync were enabled, then the SyncService shouldn't report having a
// primary (or any) account.
DCHECK(!service->IsLocalSyncEnabled());
// First check if Chrome needs to be updated.
if (service->RequiresClientUpgrade()) {
return {SYNC_ERROR, IDS_SYNC_UPGRADE_CLIENT, IDS_SYNC_UPGRADE_CLIENT_BUTTON,
UPGRADE_CLIENT};
}
// Then check for an unrecoverable error.
if (service->HasUnrecoverableError()) {
return GetStatusForUnrecoverableError(is_user_signout_allowed);
}
// Then check for an auth error.
if (auth_error.state() != GoogleServiceAuthError::NONE) {
return GetStatusForAuthError(auth_error);
}
// Check if Sync is disabled by policy.
if (service->HasDisableReason(
syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY)) {
// TODO(crbug.com/911153): Is SYNCED correct for this case?
return {SYNCED, IDS_SIGNED_IN_WITH_SYNC_DISABLED_BY_POLICY,
IDS_SETTINGS_EMPTY_STRING, NO_ACTION};
}
// Check to see if sync has been disabled via the dashboard and needs to be
// set up once again.
if (!service->GetUserSettings()->IsSyncRequested()) {
return {SYNC_ERROR, IDS_SIGNED_IN_WITH_SYNC_STOPPED_VIA_DASHBOARD,
IDS_SETTINGS_EMPTY_STRING, NO_ACTION};
}
if (service->GetUserSettings()->IsFirstSetupComplete()) {
// Check for a passphrase error.
if (service->GetUserSettings()
->IsPassphraseRequiredForPreferredDataTypes()) {
// TODO(mastiz): This should return PASSWORDS_ONLY_SYNC_ERROR if only
// passwords are encrypted as per IsEncryptEverythingEnabled().
return {SYNC_ERROR, IDS_SYNC_STATUS_NEEDS_PASSWORD,
IDS_SYNC_STATUS_NEEDS_PASSWORD_BUTTON, ENTER_PASSPHRASE};
}
if (service->IsSyncFeatureActive() &&
service->GetUserSettings()
->IsTrustedVaultKeyRequiredForPreferredDataTypes()) {
return {service->GetUserSettings()->IsEncryptEverythingEnabled()
? SYNC_ERROR
: PASSWORDS_ONLY_SYNC_ERROR,
IDS_SETTINGS_EMPTY_STRING, IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON,
RETRIEVE_TRUSTED_VAULT_KEYS};
}
// At this point, there is no Sync error.
if (service->IsSyncFeatureActive()) {
return {SYNCED,
service->GetUserSettings()->IsSyncEverythingEnabled()
? IDS_SYNC_ACCOUNT_SYNCING
: IDS_SYNC_ACCOUNT_SYNCING_CUSTOM_DATA_TYPES,
IDS_SETTINGS_EMPTY_STRING, NO_ACTION};
} else {
// Sync is still initializing.
return {SYNCED, IDS_SETTINGS_EMPTY_STRING, IDS_SETTINGS_EMPTY_STRING,
NO_ACTION};
}
}
// If first setup is in progress, show an "in progress" message.
if (service->IsSetupInProgress()) {
return {PRE_SYNCED, IDS_SYNC_SETUP_IN_PROGRESS, IDS_SETTINGS_EMPTY_STRING,
NO_ACTION};
}
// At this point we've ruled out all other cases - all that's left is a
// missing Sync confirmation.
DCHECK(ShouldRequestSyncConfirmation(service));
return {SYNC_ERROR, IDS_SYNC_SETTINGS_NOT_CONFIRMED,
IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON,
CONFIRM_SYNC_SETTINGS};
}
void FocusWebContents(Browser* browser) {
auto* const contents = browser->tab_strip_model()->GetActiveWebContents();
if (contents)
contents->Focus();
}
void OpenTabForSyncKeyRetrievalWithURL(Browser* browser, const GURL& url) {
DCHECK(browser);
FocusWebContents(browser);
NavigateParams params(GetSingletonTabNavigateParams(browser, url));
// Allow the window to close itself.
params.created_with_opener = true;
Navigate(&params);
}
// Returns true if the user has consented to browser sync-the-feature or
// Chrome OS sync.
bool HasUserOptedInToSync(const syncer::SyncUserSettings* settings) {
if (settings->IsFirstSetupComplete())
return true;
#if defined(OS_CHROMEOS)
if (chromeos::features::IsSplitSettingsSyncEnabled() &&
settings->IsOsSyncFeatureEnabled()) {
return true;
}
#endif // defined(OS_CHROMEOS)
return false;
}
} // namespace
StatusLabels GetStatusLabels(syncer::SyncService* sync_service,
signin::IdentityManager* identity_manager,
bool is_user_signout_allowed) {
if (!sync_service) {
// This can happen if Sync is disabled via the command line.
return {PRE_SYNCED, IDS_SETTINGS_EMPTY_STRING, IDS_SETTINGS_EMPTY_STRING,
NO_ACTION};
}
DCHECK(identity_manager);
CoreAccountInfo account_info = sync_service->GetAuthenticatedAccountInfo();
GoogleServiceAuthError auth_error =
identity_manager->GetErrorStateOfRefreshTokenForAccount(
account_info.account_id);
return GetStatusLabelsImpl(sync_service, is_user_signout_allowed, auth_error);
}
StatusLabels GetStatusLabels(Profile* profile) {
DCHECK(profile);
return GetStatusLabels(ProfileSyncServiceFactory::GetForProfile(profile),
IdentityManagerFactory::GetForProfile(profile),
signin_util::IsUserSignoutAllowedForProfile(profile));
}
MessageType GetStatus(Profile* profile) {
return GetStatusLabels(profile).message_type;
}
AvatarSyncErrorType GetMessagesForAvatarSyncError(
Profile* profile,
int* content_string_id,
int* button_string_id) {
const syncer::SyncService* service =
ProfileSyncServiceFactory::GetForProfile(profile);
// If there is no SyncService (probably because sync is disabled from the
// command line), then there's no error to show.
if (!service)
return NO_SYNC_ERROR;
// The order or priority is going to be: 1. Unrecoverable errors.
// 2. Auth errors. 3. Outdated client errors. 4. Passphrase errors.
// Note that an unrecoverable error is sometimes caused by the Chrome client
// being outdated; that case is handled separately below.
if (service->HasUnrecoverableError() && !service->RequiresClientUpgrade()) {
// Display different messages and buttons for managed accounts.
if (!signin_util::IsUserSignoutAllowedForProfile(profile)) {
// For a managed user, the user is directed to the signout
// confirmation dialogue in the settings page.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNOUT_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNOUT_BUTTON;
return MANAGED_USER_UNRECOVERABLE_ERROR;
}
// For a non-managed user, we sign out on the user's behalf and prompt
// the user to sign in again.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_BUTTON;
return UNRECOVERABLE_ERROR;
}
// Check for an auth error.
CoreAccountInfo account_info = service->GetAuthenticatedAccountInfo();
GoogleServiceAuthError auth_error =
IdentityManagerFactory::GetForProfile(profile)
->GetErrorStateOfRefreshTokenForAccount(account_info.account_id);
if (auth_error.state() != GoogleServiceAuthError::State::NONE) {
// The user can reauth to resolve the signin error.
*content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNIN_BUTTON;
return AUTH_ERROR;
}
// Check if the Chrome client needs to be updated.
if (service->RequiresClientUpgrade()) {
*content_string_id = IDS_SYNC_ERROR_USER_MENU_UPGRADE_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_UPGRADE_BUTTON;
return UPGRADE_CLIENT_ERROR;
}
// Check for a sync passphrase error.
if (ShouldShowPassphraseError(service)) {
*content_string_id = IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_BUTTON;
return PASSPHRASE_ERROR;
}
// Check for a sync confirmation error.
if (ShouldRequestSyncConfirmation(service)) {
*content_string_id = IDS_SYNC_SETTINGS_NOT_CONFIRMED;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON;
return SETTINGS_UNCONFIRMED_ERROR;
}
// Check for sync encryption keys missing.
if (ShouldShowSyncKeysMissingError(service)) {
*content_string_id = IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_MESSAGE;
*button_string_id = IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON;
return service->GetUserSettings()->IsEncryptEverythingEnabled()
? TRUSTED_VAULT_KEY_MISSING_FOR_EVERYTHING_ERROR
: TRUSTED_VAULT_KEY_MISSING_FOR_PASSWORDS_ERROR;
}
// There is no error.
return NO_SYNC_ERROR;
}
bool ShouldRequestSyncConfirmation(const syncer::SyncService* service) {
// This method mostly handles two situations:
// 1. The initial Sync setup was aborted without actually disabling Sync
// again. That generally shouldn't happen, but it might if Chrome crashed
// while the setup was ongoing, or due to past bugs in the setup flow.
// 2. Sync was reset from the dashboard. That usually signs out the user too,
// but it doesn't on ChromeOS, or for managed (enterprise) accounts where
// sign-out is prohibited.
// Note that we do not check IsSyncRequested() here: In situation 1 it'd
// usually be true, but in situation 2 it's false. Note that while there is a
// primary account, IsSyncRequested() can only be false if Sync was reset from
// the dashboard.
return !service->IsLocalSyncEnabled() &&
service->IsAuthenticatedAccountPrimary() &&
!service->IsSetupInProgress() &&
!service->GetUserSettings()->IsFirstSetupComplete();
}
bool ShouldShowPassphraseError(const syncer::SyncService* service) {
const syncer::SyncUserSettings* settings = service->GetUserSettings();
return HasUserOptedInToSync(settings) &&
settings->IsPassphraseRequiredForPreferredDataTypes();
}
bool ShouldShowSyncKeysMissingError(const syncer::SyncService* service) {
const syncer::SyncUserSettings* settings = service->GetUserSettings();
return HasUserOptedInToSync(settings) &&
settings->IsTrustedVaultKeyRequiredForPreferredDataTypes();
}
void OpenTabForSyncKeyRetrieval(Browser* browser) {
const GURL continue_url =
GURL(UIThreadSearchTermsData().GoogleBaseURLValue());
GURL retrieval_url = GaiaUrls::GetInstance()->signin_chrome_sync_keys_url();
if (continue_url.is_valid()) {
retrieval_url = net::AppendQueryParameter(retrieval_url, "continue",
continue_url.spec());
}
OpenTabForSyncKeyRetrievalWithURL(browser, retrieval_url);
}
void OpenTabForSyncKeyRetrievalWithURLForTesting(Browser* browser,
const GURL& url) {
OpenTabForSyncKeyRetrievalWithURL(browser, url);
}
} // namespace sync_ui_util