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 "chromeos/login/auth/online_attempt.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/location.h"
11#include "base/logging.h"
12#include "base/memory/ref_counted.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/message_loop/message_loop_proxy.h"
15#include "chromeos/login/auth/auth_attempt_state.h"
16#include "chromeos/login/auth/auth_attempt_state_resolver.h"
17#include "chromeos/login/auth/key.h"
18#include "chromeos/login/auth/user_context.h"
19#include "components/user_manager/user_type.h"
20#include "google_apis/gaia/gaia_auth_consumer.h"
21#include "google_apis/gaia/gaia_auth_fetcher.h"
22#include "google_apis/gaia/gaia_constants.h"
23#include "net/base/load_flags.h"
24#include "net/base/net_errors.h"
25#include "net/url_request/url_request_status.h"
26
27namespace chromeos {
28
29// static
30const int OnlineAttempt::kClientLoginTimeoutMs = 10000;
31
32OnlineAttempt::OnlineAttempt(AuthAttemptState* current_attempt,
33                             AuthAttemptStateResolver* callback)
34    : message_loop_(base::MessageLoopProxy::current()),
35      attempt_(current_attempt),
36      resolver_(callback),
37      try_again_(true),
38      weak_factory_(this) {
39  DCHECK(attempt_->user_type == user_manager::USER_TYPE_REGULAR);
40}
41
42OnlineAttempt::~OnlineAttempt() {
43  // Just to be sure.
44  if (client_fetcher_.get())
45    client_fetcher_->CancelRequest();
46}
47
48void OnlineAttempt::Initiate(net::URLRequestContextGetter* request_context) {
49  client_fetcher_.reset(new GaiaAuthFetcher(
50      this, GaiaConstants::kChromeOSSource, request_context));
51  message_loop_->PostTask(
52      FROM_HERE,
53      base::Bind(&OnlineAttempt::TryClientLogin, weak_factory_.GetWeakPtr()));
54}
55
56void OnlineAttempt::OnClientLoginSuccess(
57    const GaiaAuthConsumer::ClientLoginResult& unused) {
58  VLOG(1) << "Online login successful!";
59
60  weak_factory_.InvalidateWeakPtrs();
61
62  if (attempt_->hosted_policy() == GaiaAuthFetcher::HostedAccountsAllowed &&
63      attempt_->is_first_time_user()) {
64    // First time user, and we don't know if the account is HOSTED or not.
65    // Since we don't allow HOSTED accounts to log in, we need to try
66    // again, without allowing HOSTED accounts.
67    //
68    // NOTE: we used to do this in the opposite order, so that we'd only
69    // try the HOSTED pathway if GOOGLE-only failed.  This breaks CAPTCHA
70    // handling, though.
71    attempt_->DisableHosted();
72    TryClientLogin();
73    return;
74  }
75  TriggerResolve(AuthFailure::AuthFailureNone());
76}
77
78void OnlineAttempt::OnClientLoginFailure(const GoogleServiceAuthError& error) {
79  weak_factory_.InvalidateWeakPtrs();
80
81  if (error.state() == GoogleServiceAuthError::REQUEST_CANCELED) {
82    if (try_again_) {
83      try_again_ = false;
84      // TODO(cmasone): add UMA tracking for this to see if we can remove it.
85      LOG(ERROR) << "Login attempt canceled!?!?  Trying again.";
86      TryClientLogin();
87      return;
88    }
89    LOG(ERROR) << "Login attempt canceled again?  Already retried...";
90  }
91
92  if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS &&
93      attempt_->is_first_time_user() &&
94      attempt_->hosted_policy() != GaiaAuthFetcher::HostedAccountsAllowed) {
95    // This was a first-time login, we already tried allowing HOSTED accounts
96    // and succeeded.  That we've failed with INVALID_GAIA_CREDENTIALS now
97    // indicates that the account is HOSTED.
98    LOG(WARNING) << "Rejecting valid HOSTED account.";
99    TriggerResolve(AuthFailure::FromNetworkAuthFailure(
100        GoogleServiceAuthError(GoogleServiceAuthError::HOSTED_NOT_ALLOWED)));
101    return;
102  }
103
104  if (error.state() == GoogleServiceAuthError::TWO_FACTOR) {
105    LOG(WARNING) << "Two factor authenticated. Sync will not work.";
106    TriggerResolve(AuthFailure::AuthFailureNone());
107
108    return;
109  }
110  VLOG(2) << "ClientLogin attempt failed with " << error.state();
111  TriggerResolve(AuthFailure::FromNetworkAuthFailure(error));
112}
113
114void OnlineAttempt::TryClientLogin() {
115  message_loop_->PostDelayedTask(
116      FROM_HERE,
117      base::Bind(&OnlineAttempt::CancelClientLogin, weak_factory_.GetWeakPtr()),
118      base::TimeDelta::FromMilliseconds(kClientLoginTimeoutMs));
119
120  client_fetcher_->StartClientLogin(
121      attempt_->user_context.GetUserID(),
122      attempt_->user_context.GetKey()->GetSecret(),
123      GaiaConstants::kSyncService,
124      attempt_->login_token,
125      attempt_->login_captcha,
126      attempt_->hosted_policy());
127}
128
129bool OnlineAttempt::HasPendingFetch() {
130  return client_fetcher_->HasPendingFetch();
131}
132
133void OnlineAttempt::CancelRequest() {
134  weak_factory_.InvalidateWeakPtrs();
135}
136
137void OnlineAttempt::CancelClientLogin() {
138  if (HasPendingFetch()) {
139    LOG(WARNING) << "Canceling ClientLogin attempt.";
140    CancelRequest();
141
142    TriggerResolve(AuthFailure(AuthFailure::LOGIN_TIMED_OUT));
143  }
144}
145
146void OnlineAttempt::TriggerResolve(const AuthFailure& outcome) {
147  attempt_->RecordOnlineLoginStatus(outcome);
148  client_fetcher_.reset(NULL);
149  resolver_->Resolve();
150}
151
152}  // namespace chromeos
153