blob: 111f78ff46eb43b88ac0382dbbccd4df94855f10 [file] [log] [blame]
// Copyright 2015 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/chromeos/arc/arc_auth_service.h"
#include <utility>
#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/chromeos/arc/arc_auth_notification.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/prefs/pref_service_syncable_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/chromeos_switches.h"
#include "components/arc/arc_bridge_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_manager_base.h"
#include "components/syncable_prefs/pref_service_syncable.h"
#include "components/user_manager/user.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "google_apis/gaia/gaia_constants.h"
#include "ui/base/l10n/l10n_util.h"
namespace arc {
namespace {
// Weak pointer. This class is owned by ArcServiceManager.
ArcAuthService* arc_auth_service = nullptr;
const char kArcSupportExtensionId[] = "cnbgggchhmkkdmeppjobngjoejnihlei";
const char kArcSupportStorageId[] = "arc_support";
// Skip creating UI in unit tests
bool disable_ui_for_testing = false;
const char kStateStopped[] = "STOPPED";
const char kStateFetchingCode[] = "FETCHING_CODE";
const char kStateActive[] = "ACTIVE";
} // namespace
ArcAuthService::ArcAuthService(ArcBridgeService* bridge_service)
: ArcService(bridge_service), binding_(this) {
DCHECK(!arc_auth_service);
arc_auth_service = this;
arc_bridge_service()->AddObserver(this);
}
ArcAuthService::~ArcAuthService() {
DCHECK(!profile_);
arc_bridge_service()->RemoveObserver(this);
DCHECK(arc_auth_service == this);
arc_auth_service = nullptr;
}
// static
ArcAuthService* ArcAuthService::Get() {
DCHECK(arc_auth_service);
DCHECK(arc_auth_service->thread_checker_.CalledOnValidThread());
return arc_auth_service;
}
// static
void ArcAuthService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(
prefs::kArcEnabled, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterBooleanPref(prefs::kArcSignedIn, false);
}
// static
void ArcAuthService::DisableUIForTesting() {
disable_ui_for_testing = true;
}
// static
bool ArcAuthService::IsOptInVerificationDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kDisableArcOptInVerification);
}
void ArcAuthService::OnAuthInstanceReady() {
arc_bridge_service()->auth_instance()->Init(
binding_.CreateInterfacePtrAndBind());
}
std::string ArcAuthService::GetAndResetAuthCode() {
DCHECK(thread_checker_.CalledOnValidThread());
std::string auth_code;
auth_code_.swap(auth_code);
return auth_code;
}
void ArcAuthService::GetAuthCodeDeprecated(
const GetAuthCodeDeprecatedCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!IsOptInVerificationDisabled());
callback.Run(mojo::String(GetAndResetAuthCode()));
}
void ArcAuthService::GetAuthCode(const GetAuthCodeCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
const std::string auth_code = GetAndResetAuthCode();
const bool verification_disabled = IsOptInVerificationDisabled();
if (!auth_code.empty() || verification_disabled) {
callback.Run(mojo::String(auth_code), !verification_disabled);
return;
}
initial_opt_in_ = false;
auth_callback_ = callback;
SetState(State::FETCHING_CODE);
FetchAuthCode();
}
void ArcAuthService::OnSignInComplete() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(state_, State::ACTIVE);
profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true);
CloseUI();
}
void ArcAuthService::OnSignInFailed(arc::ArcSignInFailureReason reason) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(state_, State::ACTIVE);
int error_message_id;
switch (reason) {
case arc::ArcSignInFailureReason::NETWORK_ERROR:
error_message_id = IDS_ARC_SIGN_IN_NETWORK_ERROR;
break;
case arc::ArcSignInFailureReason::SERVICE_UNAVAILABLE:
error_message_id = IDS_ARC_SIGN_IN_SERVICE_UNAVAILABLE_ERROR;
break;
case arc::ArcSignInFailureReason::BAD_AUTHENTICATION:
error_message_id = IDS_ARC_SIGN_IN_BAD_AUTHENTICATION_ERROR;
break;
case arc::ArcSignInFailureReason::GMS_CORE_NOT_AVAILABLE:
error_message_id = IDS_ARC_SIGN_IN_GMS_NOT_AVAILABLE_ERROR;
break;
case arc::ArcSignInFailureReason::CLOUD_PROVISION_FLOW_FAIL:
error_message_id = IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_FAIL_ERROR;
break;
default:
error_message_id = IDS_ARC_SIGN_IN_UNKNOWN_ERROR;
}
profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, false);
ShutdownBridgeAndShowUI(UIPage::ERROR,
l10n_util::GetStringUTF16(error_message_id));
}
void ArcAuthService::GetIsAccountManaged(
const GetIsAccountManagedCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
const user_manager::User* const primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
Profile* const profile =
chromeos::ProfileHelper::Get()->GetProfileByUser(primary_user);
const policy::ProfilePolicyConnector* const profile_policy_connector =
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile);
callback.Run(profile_policy_connector->IsManaged());
}
void ArcAuthService::SetState(State state) {
if (state_ == state)
return;
state_ = state;
FOR_EACH_OBSERVER(Observer, observer_list_, OnOptInChanged(state_));
}
void ArcAuthService::OnPrimaryUserProfilePrepared(Profile* profile) {
DCHECK(profile && profile != profile_);
DCHECK(thread_checker_.CalledOnValidThread());
Shutdown();
if (profile->IsLegacySupervised()) {
VLOG(2) << "Supervised profiles are not supported in Arc.";
return;
}
profile_ = profile;
// Reuse storage used in ARC OptIn platform app.
const std::string site_url =
base::StringPrintf("%s://%s/persist?%s", content::kGuestScheme,
kArcSupportExtensionId, kArcSupportStorageId);
storage_partition_ = content::BrowserContext::GetStoragePartitionForSite(
profile_, GURL(site_url));
CHECK(storage_partition_);
// In case UI is disabled we assume that ARC is opted-in.
if (!IsOptInVerificationDisabled()) {
pref_change_registrar_.Init(profile_->GetPrefs());
pref_change_registrar_.Add(
prefs::kArcEnabled,
base::Bind(&ArcAuthService::OnOptInPreferenceChanged,
base::Unretained(this)));
if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) {
OnOptInPreferenceChanged();
} else {
if (!disable_ui_for_testing && profile_->IsNewProfile()) {
PrefServiceSyncableFromProfile(profile_)->AddObserver(this);
OnIsSyncingChanged();
}
}
} else {
auth_code_.clear();
StartArc();
}
}
void ArcAuthService::OnIsSyncingChanged() {
syncable_prefs::PrefServiceSyncable* const pref_service_syncable =
PrefServiceSyncableFromProfile(profile_);
if (!pref_service_syncable->IsSyncing())
return;
pref_service_syncable->RemoveObserver(this);
if (!profile_->GetPrefs()->HasPrefPath(prefs::kArcEnabled))
arc::ArcAuthNotification::Show();
}
void ArcAuthService::Shutdown() {
ShutdownBridgeAndCloseUI();
if (profile_)
PrefServiceSyncableFromProfile(profile_)->RemoveObserver(this);
profile_ = nullptr;
pref_change_registrar_.RemoveAll();
}
void ArcAuthService::ShowUI(UIPage page, const base::string16& status) {
if (disable_ui_for_testing || IsOptInVerificationDisabled())
return;
SetUIPage(page, status);
const extensions::AppWindowRegistry* const app_window_registry =
extensions::AppWindowRegistry::Get(profile_);
DCHECK(app_window_registry);
if (app_window_registry->GetCurrentAppWindowForApp(kArcSupportExtensionId))
return;
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
kArcSupportExtensionId);
CHECK(extension &&
extensions::util::IsAppLaunchable(kArcSupportExtensionId, profile_));
OpenApplication(CreateAppLaunchParamsUserContainer(
profile_, extension, NEW_WINDOW, extensions::SOURCE_CHROME_INTERNAL));
}
void ArcAuthService::OnMergeSessionSuccess(const std::string& data) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!initial_opt_in_);
context_prepared_ = true;
ShowUI(UIPage::LSO_PROGRESS, base::string16());
}
void ArcAuthService::OnMergeSessionFailure(
const GoogleServiceAuthError& error) {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(2) << "Failed to merge gaia session " << error.ToString() << ".";
OnAuthCodeFailed();
}
void ArcAuthService::OnUbertokenSuccess(const std::string& token) {
DCHECK(thread_checker_.CalledOnValidThread());
merger_fetcher_.reset(
new GaiaAuthFetcher(this, GaiaConstants::kChromeOSSource,
storage_partition_->GetURLRequestContext()));
merger_fetcher_->StartMergeSession(token, std::string());
}
void ArcAuthService::OnUbertokenFailure(const GoogleServiceAuthError& error) {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(2) << "Failed to get ubertoken " << error.ToString() << ".";
OnAuthCodeFailed();
}
void ArcAuthService::OnOptInPreferenceChanged() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(profile_);
if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) {
if (state_ != State::ACTIVE) {
CloseUI();
auth_code_.clear();
if (!profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn)) {
// Need pre-fetch auth code and show OptIn UI if needed.
initial_opt_in_ = true;
SetState(State::FETCHING_CODE);
FetchAuthCode();
} else {
// Ready to start Arc.
StartArc();
}
}
} else {
ShutdownBridgeAndCloseUI();
}
}
void ArcAuthService::ShutdownBridge() {
auth_callback_.reset();
auth_fetcher_.reset();
ubertoken_fethcher_.reset();
merger_fetcher_.reset();
ArcBridgeService::Get()->Shutdown();
SetState(State::STOPPED);
}
void ArcAuthService::ShutdownBridgeAndCloseUI() {
ShutdownBridge();
CloseUI();
}
void ArcAuthService::ShutdownBridgeAndShowUI(UIPage page,
const base::string16& status) {
ShutdownBridge();
ShowUI(page, status);
}
void ArcAuthService::AddObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observer_list_.AddObserver(observer);
}
void ArcAuthService::RemoveObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observer_list_.RemoveObserver(observer);
}
void ArcAuthService::CloseUI() {
FOR_EACH_OBSERVER(Observer, observer_list_, OnOptInUIClose());
}
void ArcAuthService::SetUIPage(UIPage page, const base::string16& status) {
ui_page_ = page;
ui_page_status_ = status;
FOR_EACH_OBSERVER(Observer, observer_list_,
OnOptInUIShowPage(ui_page_, ui_page_status_));
}
void ArcAuthService::StartArc() {
DCHECK(thread_checker_.CalledOnValidThread());
ArcBridgeService::Get()->HandleStartup();
SetState(State::ACTIVE);
}
void ArcAuthService::SetAuthCodeAndStartArc(const std::string& auth_code) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!auth_code.empty());
if (!auth_callback_.is_null()) {
DCHECK_EQ(state_, State::FETCHING_CODE);
SetState(State::ACTIVE);
auth_callback_.Run(mojo::String(auth_code), !IsOptInVerificationDisabled());
auth_callback_.reset();
return;
}
State state = state_;
if (state != State::FETCHING_CODE) {
ShutdownBridgeAndCloseUI();
return;
}
SetUIPage(UIPage::START_PROGRESS, base::string16());
ShutdownBridge();
auth_code_ = auth_code;
StartArc();
}
void ArcAuthService::CheckAuthCode() {
DCHECK(thread_checker_.CalledOnValidThread());
initial_opt_in_ = false;
SetState(State::FETCHING_CODE);
FetchAuthCode();
}
void ArcAuthService::FetchAuthCode() {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != State::FETCHING_CODE)
return;
auth_fetcher_.reset(
new ArcAuthFetcher(storage_partition_->GetURLRequestContext(), this));
}
void ArcAuthService::CancelAuthCode() {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != State::FETCHING_CODE)
return;
DisableArc();
}
void ArcAuthService::EnableArc() {
DCHECK(thread_checker_.CalledOnValidThread());
profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, true);
}
void ArcAuthService::DisableArc() {
DCHECK(thread_checker_.CalledOnValidThread());
profile_->GetPrefs()->SetBoolean(prefs::kArcEnabled, false);
}
void ArcAuthService::OnAuthCodeFetched(const std::string& auth_code) {
DCHECK_EQ(state_, State::FETCHING_CODE);
SetAuthCodeAndStartArc(auth_code);
}
void ArcAuthService::PrepareContext() {
DCHECK(thread_checker_.CalledOnValidThread());
// Get auth token to continue.
ProfileOAuth2TokenService* token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(profile_);
CHECK(token_service && signin_manager);
const std::string& account_id = signin_manager->GetAuthenticatedAccountId();
ubertoken_fethcher_.reset(
new UbertokenFetcher(token_service, this, GaiaConstants::kChromeOSSource,
storage_partition_->GetURLRequestContext()));
ubertoken_fethcher_->StartFetchingToken(account_id);
}
void ArcAuthService::OnAuthCodeNeedUI() {
DCHECK(thread_checker_.CalledOnValidThread());
if (initial_opt_in_) {
initial_opt_in_ = false;
ShowUI(UIPage::START, base::string16());
} else if (context_prepared_) {
ShowUI(UIPage::LSO_PROGRESS, base::string16());
} else {
PrepareContext();
}
}
void ArcAuthService::OnAuthCodeFailed() {
DCHECK_EQ(state_, State::FETCHING_CODE);
if (initial_opt_in_) {
// Don't show error as first page.
initial_opt_in_ = false;
ShutdownBridgeAndShowUI(UIPage::START, base::string16());
} else {
ShutdownBridgeAndShowUI(
UIPage::ERROR,
l10n_util::GetStringUTF16(IDS_ARC_SERVER_COMMUNICATION_ERROR));
}
}
std::ostream& operator<<(std::ostream& os, const ArcAuthService::State& state) {
switch (state) {
case ArcAuthService::State::STOPPED:
return os << kStateStopped;
case ArcAuthService::State::FETCHING_CODE:
return os << kStateFetchingCode;
case ArcAuthService::State::ACTIVE:
return os << kStateActive;
default:
NOTREACHED();
return os;
}
}
} // namespace arc