blob: 0a3dd4fc9f457569c9637afe3707ab7402901924 [file] [log] [blame]
// Copyright 2014 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/signin/easy_unlock_service_signin_chromeos.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/sys_info.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_metrics.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chromeos/login/auth/user_context.h"
namespace {
// The maximum allowed backoff interval when waiting for cryptohome to start.
uint32 kMaxCryptohomeBackoffIntervalMs = 10000u;
// If the data load fails, the initial interval after which the load will be
// retried. Further intervals will exponentially increas by factor 2.
uint32 kInitialCryptohomeBackoffIntervalMs = 200u;
// Calculates the backoff interval that should be used next.
// |backoff| The last backoff interval used.
uint32 GetNextBackoffInterval(uint32 backoff) {
if (backoff == 0u)
return kInitialCryptohomeBackoffIntervalMs;
return backoff * 2;
}
void LoadDataForUser(
const std::string& user_id,
uint32 backoff_ms,
const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback);
// Callback passed to |LoadDataForUser()|.
// If |LoadDataForUser| function succeeded, it invokes |callback| with the
// results.
// If |LoadDataForUser| failed and further retries are allowed, schedules new
// |LoadDataForUser| call with some backoff. If no further retires are allowed,
// it invokes |callback| with the |LoadDataForUser| results.
void RetryDataLoadOnError(
const std::string& user_id,
uint32 backoff_ms,
const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback,
bool success,
const chromeos::EasyUnlockDeviceKeyDataList& data_list) {
if (success) {
callback.Run(success, data_list);
return;
}
uint32 next_backoff_ms = GetNextBackoffInterval(backoff_ms);
if (next_backoff_ms > kMaxCryptohomeBackoffIntervalMs) {
callback.Run(false, data_list);
return;
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&LoadDataForUser, user_id, next_backoff_ms, callback),
base::TimeDelta::FromMilliseconds(next_backoff_ms));
}
// Loads device data list associated with the user's Easy unlock keys.
void LoadDataForUser(
const std::string& user_id,
uint32 backoff_ms,
const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback) {
chromeos::EasyUnlockKeyManager* key_manager =
chromeos::UserSessionManager::GetInstance()->GetEasyUnlockKeyManager();
DCHECK(key_manager);
key_manager->GetDeviceDataList(
chromeos::UserContext(user_id),
base::Bind(&RetryDataLoadOnError, user_id, backoff_ms, callback));
}
} // namespace
EasyUnlockServiceSignin::UserData::UserData()
: state(EasyUnlockServiceSignin::USER_DATA_STATE_INITIAL) {
}
EasyUnlockServiceSignin::UserData::~UserData() {}
EasyUnlockServiceSignin::EasyUnlockServiceSignin(Profile* profile)
: EasyUnlockService(profile),
allow_cryptohome_backoff_(true),
service_active_(false),
weak_ptr_factory_(this) {
}
EasyUnlockServiceSignin::~EasyUnlockServiceSignin() {
}
EasyUnlockService::Type EasyUnlockServiceSignin::GetType() const {
return EasyUnlockService::TYPE_SIGNIN;
}
std::string EasyUnlockServiceSignin::GetUserEmail() const {
return user_id_;
}
void EasyUnlockServiceSignin::LaunchSetup() {
NOTREACHED();
}
const base::DictionaryValue* EasyUnlockServiceSignin::GetPermitAccess() const {
return NULL;
}
void EasyUnlockServiceSignin::SetPermitAccess(
const base::DictionaryValue& permit) {
NOTREACHED();
}
void EasyUnlockServiceSignin::ClearPermitAccess() {
NOTREACHED();
}
const base::ListValue* EasyUnlockServiceSignin::GetRemoteDevices() const {
const UserData* data = FindLoadedDataForCurrentUser();
if (!data)
return NULL;
return &data->remote_devices_value;
}
void EasyUnlockServiceSignin::SetRemoteDevices(
const base::ListValue& devices) {
NOTREACHED();
}
void EasyUnlockServiceSignin::ClearRemoteDevices() {
NOTREACHED();
}
void EasyUnlockServiceSignin::RunTurnOffFlow() {
NOTREACHED();
}
void EasyUnlockServiceSignin::ResetTurnOffFlow() {
NOTREACHED();
}
EasyUnlockService::TurnOffFlowStatus
EasyUnlockServiceSignin::GetTurnOffFlowStatus() const {
return EasyUnlockService::IDLE;
}
std::string EasyUnlockServiceSignin::GetChallenge() const {
const UserData* data = FindLoadedDataForCurrentUser();
// TODO(xiyuan): Use correct remote device instead of hard coded first one.
uint32 device_index = 0;
if (!data || data->devices.size() <= device_index)
return std::string();
return data->devices[device_index].challenge;
}
std::string EasyUnlockServiceSignin::GetWrappedSecret() const {
const UserData* data = FindLoadedDataForCurrentUser();
// TODO(xiyuan): Use correct remote device instead of hard coded first one.
uint32 device_index = 0;
if (!data || data->devices.size() <= device_index)
return std::string();
return data->devices[device_index].wrapped_secret;
}
void EasyUnlockServiceSignin::RecordEasySignInOutcome(
const std::string& user_id,
bool success) const {
DCHECK_EQ(GetUserEmail(), user_id);
chromeos::RecordEasyUnlockLoginEvent(success
? chromeos::EASY_SIGN_IN_SUCCESS
: chromeos::EASY_SIGN_IN_FAILURE);
DVLOG(1) << "Easy sign-in " << (success ? "success" : "failure");
}
void EasyUnlockServiceSignin::RecordPasswordLoginEvent(
const std::string& user_id) const {
// This happens during tests where user could login without pod focusing.
if (GetUserEmail() != user_id)
return;
chromeos::EasyUnlockLoginEvent event =
chromeos::EASY_SIGN_IN_LOGIN_EVENT_COUNT;
if (!GetRemoteDevices() ||
GetHardlockState() == EasyUnlockScreenlockStateHandler::NO_PAIRING) {
event = chromeos::PASSWORD_SIGN_IN_NO_PAIRING;
} else if (GetHardlockState() ==
EasyUnlockScreenlockStateHandler::PAIRING_CHANGED) {
event = chromeos::PASSWORD_SIGN_IN_PAIRING_CHANGED;
} else if (GetHardlockState() ==
EasyUnlockScreenlockStateHandler::USER_HARDLOCK) {
event = chromeos::PASSWORD_SIGN_IN_USER_HARDLOCK;
} else if (!screenlock_state_handler()) {
event = chromeos::PASSWORD_SIGN_IN_SERVICE_NOT_ACTIVE;
} else {
switch (screenlock_state_handler()->state()) {
case EasyUnlockScreenlockStateHandler::STATE_INACTIVE:
event = chromeos::PASSWORD_SIGN_IN_SERVICE_NOT_ACTIVE;
break;
case EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH:
event = chromeos::PASSWORD_SIGN_IN_NO_BLUETOOTH;
break;
case EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING:
event = chromeos::PASSWORD_SIGN_IN_BLUETOOTH_CONNECTING;
break;
case EasyUnlockScreenlockStateHandler::STATE_NO_PHONE:
event = chromeos::PASSWORD_SIGN_IN_NO_PHONE;
break;
case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED:
event = chromeos::PASSWORD_SIGN_IN_PHONE_NOT_AUTHENTICATED;
break;
case EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED:
event = chromeos::PASSWORD_SIGN_IN_PHONE_LOCKED;
break;
case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE:
event = chromeos::PASSWORD_SIGN_IN_PHONE_NOT_LOCKABLE;
break;
case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNSUPPORTED:
event = chromeos::PASSWORD_SIGN_IN_PHONE_UNSUPPORTED;
break;
case EasyUnlockScreenlockStateHandler::STATE_RSSI_TOO_LOW:
event = chromeos::PASSWORD_SIGN_IN_RSSI_TOO_LOW;
break;
case EasyUnlockScreenlockStateHandler::STATE_TX_POWER_TOO_HIGH:
event = chromeos::PASSWORD_SIGN_IN_TX_POWER_TOO_HIGH;
break;
case EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED:
event = chromeos::PASSWORD_SIGN_IN_WITH_AUTHENTICATED_PHONE;
break;
}
}
chromeos::RecordEasyUnlockLoginEvent(event);
DVLOG(1) << "EasySignIn password login event, event=" << event;
}
void EasyUnlockServiceSignin::InitializeInternal() {
if (chromeos::LoginState::Get()->IsUserLoggedIn())
return;
service_active_ = true;
chromeos::LoginState::Get()->AddObserver(this);
ScreenlockBridge* screenlock_bridge = ScreenlockBridge::Get();
screenlock_bridge->AddObserver(this);
if (!screenlock_bridge->focused_user_id().empty())
OnFocusedUserChanged(screenlock_bridge->focused_user_id());
}
void EasyUnlockServiceSignin::ShutdownInternal() {
if (!service_active_)
return;
service_active_ = false;
weak_ptr_factory_.InvalidateWeakPtrs();
ScreenlockBridge::Get()->RemoveObserver(this);
chromeos::LoginState::Get()->RemoveObserver(this);
STLDeleteContainerPairSecondPointers(user_data_.begin(), user_data_.end());
user_data_.clear();
}
bool EasyUnlockServiceSignin::IsAllowedInternal() {
return service_active_ &&
!user_id_.empty() &&
!chromeos::LoginState::Get()->IsUserLoggedIn();
}
void EasyUnlockServiceSignin::OnScreenDidLock() {
// Update initial UI is when the account picker on login screen is ready.
ShowInitialUserState();
}
void EasyUnlockServiceSignin::OnScreenDidUnlock() {
Shutdown();
}
void EasyUnlockServiceSignin::OnFocusedUserChanged(const std::string& user_id) {
if (user_id_ == user_id)
return;
// Setting or clearing the user_id may changed |IsAllowed| value, so in these
// cases update the app state. Otherwise, it's enough to notify the app the
// user data has been updated.
bool should_update_app_state = user_id_.empty() != user_id.empty();
user_id_ = user_id;
ResetScreenlockState();
ShowInitialUserState();
if (should_update_app_state) {
UpdateAppState();
} else {
NotifyUserUpdated();
}
LoadCurrentUserDataIfNeeded();
}
void EasyUnlockServiceSignin::LoggedInStateChanged() {
if (!chromeos::LoginState::Get()->IsUserLoggedIn())
return;
UnloadApp();
}
void EasyUnlockServiceSignin::LoadCurrentUserDataIfNeeded() {
// TODO(xiyuan): Revisit this when adding tests.
if (!base::SysInfo::IsRunningOnChromeOS())
return;
if (user_id_.empty() || !service_active_)
return;
std::map<std::string, UserData*>::iterator it = user_data_.find(user_id_);
if (it == user_data_.end())
user_data_.insert(std::make_pair(user_id_, new UserData()));
UserData* data = user_data_[user_id_];
if (data->state != USER_DATA_STATE_INITIAL)
return;
data->state = USER_DATA_STATE_LOADING;
LoadDataForUser(
user_id_,
allow_cryptohome_backoff_ ? 0u : kMaxCryptohomeBackoffIntervalMs,
base::Bind(&EasyUnlockServiceSignin::OnUserDataLoaded,
weak_ptr_factory_.GetWeakPtr(),
user_id_));
}
void EasyUnlockServiceSignin::OnUserDataLoaded(
const std::string& user_id,
bool success,
const chromeos::EasyUnlockDeviceKeyDataList& devices) {
allow_cryptohome_backoff_ = false;
UserData* data = user_data_[user_id_];
data->state = USER_DATA_STATE_LOADED;
if (success) {
data->devices = devices;
chromeos::EasyUnlockKeyManager::DeviceDataListToRemoteDeviceList(
user_id, devices, &data->remote_devices_value);
}
// If the fetched data belongs to the currently focused user, notify the app
// that it has to refresh it's user data.
if (user_id == user_id_)
NotifyUserUpdated();
}
const EasyUnlockServiceSignin::UserData*
EasyUnlockServiceSignin::FindLoadedDataForCurrentUser() const {
if (user_id_.empty())
return NULL;
std::map<std::string, UserData*>::const_iterator it =
user_data_.find(user_id_);
if (it == user_data_.end())
return NULL;
if (it->second->state != USER_DATA_STATE_LOADED)
return NULL;
return it->second;
}