1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/signin/easy_unlock_service_signin_chromeos.h"
6
7#include "base/basictypes.h"
8#include "base/bind.h"
9#include "base/location.h"
10#include "base/logging.h"
11#include "base/stl_util.h"
12#include "base/thread_task_runner_handle.h"
13#include "base/time/time.h"
14#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h"
15#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_metrics.h"
16#include "chrome/browser/chromeos/login/session/user_session_manager.h"
17#include "chromeos/login/auth/user_context.h"
18
19namespace {
20
21// The maximum allowed backoff interval when waiting for cryptohome to start.
22uint32 kMaxCryptohomeBackoffIntervalMs = 10000u;
23
24// If the data load fails, the initial interval after which the load will be
25// retried. Further intervals will exponentially increas by factor 2.
26uint32 kInitialCryptohomeBackoffIntervalMs = 200u;
27
28// Calculates the backoff interval that should be used next.
29// |backoff| The last backoff interval used.
30uint32 GetNextBackoffInterval(uint32 backoff) {
31  if (backoff == 0u)
32    return kInitialCryptohomeBackoffIntervalMs;
33  return backoff * 2;
34}
35
36void LoadDataForUser(
37    const std::string& user_id,
38    uint32 backoff_ms,
39    const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback);
40
41// Callback passed to |LoadDataForUser()|.
42// If |LoadDataForUser| function succeeded, it invokes |callback| with the
43// results.
44// If |LoadDataForUser| failed and further retries are allowed, schedules new
45// |LoadDataForUser| call with some backoff. If no further retires are allowed,
46// it invokes |callback| with the |LoadDataForUser| results.
47void RetryDataLoadOnError(
48    const std::string& user_id,
49    uint32 backoff_ms,
50    const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback,
51    bool success,
52    const chromeos::EasyUnlockDeviceKeyDataList& data_list) {
53  if (success) {
54    callback.Run(success, data_list);
55    return;
56  }
57
58  uint32 next_backoff_ms = GetNextBackoffInterval(backoff_ms);
59  if (next_backoff_ms > kMaxCryptohomeBackoffIntervalMs) {
60    callback.Run(false, data_list);
61    return;
62  }
63
64  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
65      FROM_HERE,
66      base::Bind(&LoadDataForUser, user_id, next_backoff_ms, callback),
67      base::TimeDelta::FromMilliseconds(next_backoff_ms));
68}
69
70// Loads device data list associated with the user's Easy unlock keys.
71void LoadDataForUser(
72    const std::string& user_id,
73    uint32 backoff_ms,
74    const chromeos::EasyUnlockKeyManager::GetDeviceDataListCallback& callback) {
75  chromeos::EasyUnlockKeyManager* key_manager =
76      chromeos::UserSessionManager::GetInstance()->GetEasyUnlockKeyManager();
77  DCHECK(key_manager);
78
79  key_manager->GetDeviceDataList(
80      chromeos::UserContext(user_id),
81      base::Bind(&RetryDataLoadOnError, user_id, backoff_ms, callback));
82}
83
84}  // namespace
85
86EasyUnlockServiceSignin::UserData::UserData()
87    : state(EasyUnlockServiceSignin::USER_DATA_STATE_INITIAL) {
88}
89
90EasyUnlockServiceSignin::UserData::~UserData() {}
91
92EasyUnlockServiceSignin::EasyUnlockServiceSignin(Profile* profile)
93    : EasyUnlockService(profile),
94      allow_cryptohome_backoff_(true),
95      service_active_(false),
96      weak_ptr_factory_(this) {
97}
98
99EasyUnlockServiceSignin::~EasyUnlockServiceSignin() {
100}
101
102EasyUnlockService::Type EasyUnlockServiceSignin::GetType() const {
103  return EasyUnlockService::TYPE_SIGNIN;
104}
105
106std::string EasyUnlockServiceSignin::GetUserEmail() const {
107  return user_id_;
108}
109
110void EasyUnlockServiceSignin::LaunchSetup() {
111  NOTREACHED();
112}
113
114const base::DictionaryValue* EasyUnlockServiceSignin::GetPermitAccess() const {
115  return NULL;
116}
117
118void EasyUnlockServiceSignin::SetPermitAccess(
119    const base::DictionaryValue& permit) {
120  NOTREACHED();
121}
122
123void EasyUnlockServiceSignin::ClearPermitAccess() {
124  NOTREACHED();
125}
126
127const base::ListValue* EasyUnlockServiceSignin::GetRemoteDevices() const {
128  const UserData* data = FindLoadedDataForCurrentUser();
129  if (!data)
130    return NULL;
131  return &data->remote_devices_value;
132}
133
134void EasyUnlockServiceSignin::SetRemoteDevices(
135    const base::ListValue& devices) {
136  NOTREACHED();
137}
138
139void EasyUnlockServiceSignin::ClearRemoteDevices() {
140  NOTREACHED();
141}
142
143void EasyUnlockServiceSignin::RunTurnOffFlow() {
144  NOTREACHED();
145}
146
147void EasyUnlockServiceSignin::ResetTurnOffFlow() {
148  NOTREACHED();
149}
150
151EasyUnlockService::TurnOffFlowStatus
152    EasyUnlockServiceSignin::GetTurnOffFlowStatus() const {
153  return EasyUnlockService::IDLE;
154}
155
156std::string EasyUnlockServiceSignin::GetChallenge() const {
157  const UserData* data = FindLoadedDataForCurrentUser();
158  // TODO(xiyuan): Use correct remote device instead of hard coded first one.
159  uint32 device_index = 0;
160  if (!data || data->devices.size() <= device_index)
161    return std::string();
162  return data->devices[device_index].challenge;
163}
164
165std::string EasyUnlockServiceSignin::GetWrappedSecret() const {
166  const UserData* data = FindLoadedDataForCurrentUser();
167  // TODO(xiyuan): Use correct remote device instead of hard coded first one.
168  uint32 device_index = 0;
169  if (!data || data->devices.size() <= device_index)
170    return std::string();
171  return data->devices[device_index].wrapped_secret;
172}
173
174void EasyUnlockServiceSignin::RecordEasySignInOutcome(
175    const std::string& user_id,
176    bool success) const {
177  DCHECK_EQ(GetUserEmail(), user_id);
178
179  chromeos::RecordEasyUnlockLoginEvent(success
180                                           ? chromeos::EASY_SIGN_IN_SUCCESS
181                                           : chromeos::EASY_SIGN_IN_FAILURE);
182  VLOG(1) << "Easy sign-in " << (success ? "success" : "failure");
183}
184
185void EasyUnlockServiceSignin::RecordPasswordLoginEvent(
186    const std::string& user_id) const {
187  DCHECK_EQ(GetUserEmail(), user_id);
188
189  chromeos::EasyUnlockLoginEvent event =
190      chromeos::EASY_SIGN_IN_LOGIN_EVENT_COUNT;
191  if (!GetRemoteDevices() ||
192      GetHardlockState() == EasyUnlockScreenlockStateHandler::NO_PAIRING) {
193    event = chromeos::PASSWORD_SIGN_IN_NO_PAIRING;
194  } else if (GetHardlockState() ==
195             EasyUnlockScreenlockStateHandler::PAIRING_CHANGED) {
196    event = chromeos::PASSWORD_SIGN_IN_PAIRING_CHANGED;
197  } else if (GetHardlockState() ==
198             EasyUnlockScreenlockStateHandler::USER_HARDLOCK) {
199    event = chromeos::PASSWORD_SIGN_IN_USER_HARDLOCK;
200  } else if (!screenlock_state_handler()) {
201    event = chromeos::PASSWORD_SIGN_IN_SERVICE_NOT_ACTIVE;
202  } else {
203    switch (screenlock_state_handler()->state()) {
204      case EasyUnlockScreenlockStateHandler::STATE_INACTIVE:
205        event = chromeos::PASSWORD_SIGN_IN_SERVICE_NOT_ACTIVE;
206        break;
207      case EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH:
208        event = chromeos::PASSWORD_SIGN_IN_NO_BLUETOOTH;
209        break;
210      case EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING:
211        event = chromeos::PASSWORD_SIGN_IN_BLUETOOTH_CONNECTING;
212        break;
213      case EasyUnlockScreenlockStateHandler::STATE_NO_PHONE:
214        event = chromeos::PASSWORD_SIGN_IN_NO_PHONE;
215        break;
216      case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED:
217        event = chromeos::PASSWORD_SIGN_IN_PHONE_NOT_AUTHENTICATED;
218        break;
219      case EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED:
220        event = chromeos::PASSWORD_SIGN_IN_PHONE_LOCKED;
221        break;
222      case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE:
223        event = chromeos::PASSWORD_SIGN_IN_PHONE_NOT_LOCKABLE;
224        break;
225      case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_NEARBY:
226        event = chromeos::PASSWORD_SIGN_IN_PHONE_NOT_NEARBY;
227        break;
228      case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNSUPPORTED:
229        event = chromeos::PASSWORD_SIGN_IN_PHONE_UNSUPPORTED;
230        break;
231      case EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED:
232        event = chromeos::PASSWORD_SIGN_IN_WITH_AUTHENTICATED_PHONE;
233        break;
234    }
235  }
236
237  chromeos::RecordEasyUnlockLoginEvent(event);
238  VLOG(1) << "EasySignIn password login event, event=" << event;
239}
240
241void EasyUnlockServiceSignin::InitializeInternal() {
242  if (chromeos::LoginState::Get()->IsUserLoggedIn())
243    return;
244
245  service_active_ = true;
246
247  chromeos::LoginState::Get()->AddObserver(this);
248  ScreenlockBridge* screenlock_bridge = ScreenlockBridge::Get();
249  screenlock_bridge->AddObserver(this);
250  if (!screenlock_bridge->focused_user_id().empty())
251    OnFocusedUserChanged(screenlock_bridge->focused_user_id());
252}
253
254void EasyUnlockServiceSignin::ShutdownInternal() {
255  if (!service_active_)
256    return;
257  service_active_ = false;
258
259  weak_ptr_factory_.InvalidateWeakPtrs();
260  ScreenlockBridge::Get()->RemoveObserver(this);
261  chromeos::LoginState::Get()->RemoveObserver(this);
262  STLDeleteContainerPairSecondPointers(user_data_.begin(), user_data_.end());
263  user_data_.clear();
264}
265
266bool EasyUnlockServiceSignin::IsAllowedInternal() {
267  return service_active_ &&
268         !user_id_.empty() &&
269         !chromeos::LoginState::Get()->IsUserLoggedIn();
270}
271
272void EasyUnlockServiceSignin::OnScreenDidLock() {
273  // Update initial UI is when the account picker on login screen is ready.
274  ShowInitialUserState();
275}
276
277void EasyUnlockServiceSignin::OnScreenDidUnlock() {
278  Shutdown();
279}
280
281void EasyUnlockServiceSignin::OnFocusedUserChanged(const std::string& user_id) {
282  if (user_id_ == user_id)
283    return;
284
285  // Setting or clearing the user_id may changed |IsAllowed| value, so in these
286  // cases update the app state. Otherwise, it's enough to notify the app the
287  // user data has been updated.
288  bool should_update_app_state = user_id_.empty() != user_id.empty();
289  user_id_ = user_id;
290
291  ResetScreenlockState();
292  ShowInitialUserState();
293
294  if (should_update_app_state) {
295    UpdateAppState();
296  } else {
297    NotifyUserUpdated();
298  }
299
300  LoadCurrentUserDataIfNeeded();
301}
302
303void EasyUnlockServiceSignin::LoggedInStateChanged() {
304  if (!chromeos::LoginState::Get()->IsUserLoggedIn())
305    return;
306  UnloadApp();
307}
308
309void EasyUnlockServiceSignin::LoadCurrentUserDataIfNeeded() {
310  if (user_id_.empty() || !service_active_)
311    return;
312
313  std::map<std::string, UserData*>::iterator it = user_data_.find(user_id_);
314  if (it == user_data_.end())
315    user_data_.insert(std::make_pair(user_id_, new UserData()));
316
317  UserData* data = user_data_[user_id_];
318
319  if (data->state != USER_DATA_STATE_INITIAL)
320    return;
321  data->state = USER_DATA_STATE_LOADING;
322
323  LoadDataForUser(
324      user_id_,
325      allow_cryptohome_backoff_ ? 0u : kMaxCryptohomeBackoffIntervalMs,
326      base::Bind(&EasyUnlockServiceSignin::OnUserDataLoaded,
327                 weak_ptr_factory_.GetWeakPtr(),
328                 user_id_));
329}
330
331void EasyUnlockServiceSignin::OnUserDataLoaded(
332    const std::string& user_id,
333    bool success,
334    const chromeos::EasyUnlockDeviceKeyDataList& devices) {
335  allow_cryptohome_backoff_ = false;
336
337  UserData* data = user_data_[user_id_];
338  data->state = USER_DATA_STATE_LOADED;
339  if (success) {
340    data->devices = devices;
341    chromeos::EasyUnlockKeyManager::DeviceDataListToRemoteDeviceList(
342        user_id, devices, &data->remote_devices_value);
343  }
344
345  // If the fetched data belongs to the currently focused user, notify the app
346  // that it has to refresh it's user data.
347  if (user_id == user_id_)
348    NotifyUserUpdated();
349}
350
351const EasyUnlockServiceSignin::UserData*
352    EasyUnlockServiceSignin::FindLoadedDataForCurrentUser() const {
353  if (user_id_.empty())
354    return NULL;
355
356  std::map<std::string, UserData*>::const_iterator it =
357      user_data_.find(user_id_);
358  if (it == user_data_.end())
359    return NULL;
360  if (it->second->state != USER_DATA_STATE_LOADED)
361    return NULL;
362  return it->second;
363}
364