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/chromeos/login/signin/oauth2_login_manager.h"
6
7#include <utility>
8#include <vector>
9
10#include "base/command_line.h"
11#include "base/metrics/histogram.h"
12#include "base/prefs/pref_service.h"
13#include "base/strings/string_util.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/signin/chrome_signin_client_factory.h"
17#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
18#include "chrome/browser/signin/signin_manager_factory.h"
19#include "chrome/common/chrome_switches.h"
20#include "chromeos/chromeos_switches.h"
21#include "components/signin/core/browser/profile_oauth2_token_service.h"
22#include "components/signin/core/browser/signin_client.h"
23#include "components/signin/core/browser/signin_manager.h"
24#include "components/user_manager/user_manager.h"
25#include "google_apis/gaia/gaia_auth_util.h"
26#include "google_apis/gaia/gaia_constants.h"
27#include "google_apis/gaia/gaia_urls.h"
28#include "net/url_request/url_request_context_getter.h"
29
30namespace chromeos {
31
32namespace {
33
34static const char kServiceScopeGetUserInfo[] =
35    "https://www.googleapis.com/auth/userinfo.email";
36static const int kMaxRetries = 5;
37
38}  // namespace
39
40OAuth2LoginManager::OAuth2LoginManager(Profile* user_profile)
41    : user_profile_(user_profile),
42      restore_strategy_(RESTORE_FROM_COOKIE_JAR),
43      state_(SESSION_RESTORE_NOT_STARTED) {
44  GetTokenService()->AddObserver(this);
45
46  // For telemetry, we mark session restore completed to avoid warnings from
47  // MergeSessionThrottle.
48  if (CommandLine::ForCurrentProcess()->
49          HasSwitch(chromeos::switches::kDisableGaiaServices)) {
50    SetSessionRestoreState(SESSION_RESTORE_DONE);
51  }
52}
53
54OAuth2LoginManager::~OAuth2LoginManager() {
55}
56
57void OAuth2LoginManager::AddObserver(OAuth2LoginManager::Observer* observer) {
58  observer_list_.AddObserver(observer);
59}
60
61void OAuth2LoginManager::RemoveObserver(
62    OAuth2LoginManager::Observer* observer) {
63  observer_list_.RemoveObserver(observer);
64}
65
66void OAuth2LoginManager::RestoreSession(
67    net::URLRequestContextGetter* auth_request_context,
68    SessionRestoreStrategy restore_strategy,
69    const std::string& oauth2_refresh_token,
70    const std::string& auth_code) {
71  DCHECK(user_profile_);
72  auth_request_context_ = auth_request_context;
73  restore_strategy_ = restore_strategy;
74  refresh_token_ = oauth2_refresh_token;
75  oauthlogin_access_token_ = std::string();
76  auth_code_ = auth_code;
77  session_restore_start_ = base::Time::Now();
78  SetSessionRestoreState(OAuth2LoginManager::SESSION_RESTORE_PREPARING);
79  ContinueSessionRestore();
80}
81
82void OAuth2LoginManager::ContinueSessionRestore() {
83  if (restore_strategy_ == RESTORE_FROM_COOKIE_JAR ||
84      restore_strategy_ == RESTORE_FROM_AUTH_CODE) {
85    FetchOAuth2Tokens();
86    return;
87  }
88
89  // Save passed OAuth2 refresh token.
90  if (restore_strategy_ == RESTORE_FROM_PASSED_OAUTH2_REFRESH_TOKEN) {
91    DCHECK(!refresh_token_.empty());
92    restore_strategy_ = RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN;
93    StoreOAuth2Token();
94    return;
95  }
96
97  DCHECK(restore_strategy_ == RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN);
98  RestoreSessionFromSavedTokens();
99}
100
101void OAuth2LoginManager::RestoreSessionFromSavedTokens() {
102  ProfileOAuth2TokenService* token_service = GetTokenService();
103  const std::string& primary_account_id = GetPrimaryAccountId();
104  if (token_service->RefreshTokenIsAvailable(primary_account_id)) {
105    VLOG(1) << "OAuth2 refresh token is already loaded.";
106    VerifySessionCookies();
107  } else {
108    VLOG(1) << "Loading OAuth2 refresh token from database.";
109
110    // Flag user with unknown token status in case there are no saved tokens
111    // and OnRefreshTokenAvailable is not called. Flagging it here would
112    // cause user to go through Gaia in next login to obtain a new refresh
113    // token.
114    user_manager::UserManager::Get()->SaveUserOAuthStatus(
115        primary_account_id, user_manager::User::OAUTH_TOKEN_STATUS_UNKNOWN);
116
117    token_service->LoadCredentials(primary_account_id);
118  }
119}
120
121void OAuth2LoginManager::Stop() {
122  oauth2_token_fetcher_.reset();
123  login_verifier_.reset();
124}
125
126bool OAuth2LoginManager::ShouldBlockTabLoading() {
127  return state_ == SESSION_RESTORE_PREPARING ||
128         state_ == SESSION_RESTORE_IN_PROGRESS;
129}
130
131void OAuth2LoginManager::OnRefreshTokenAvailable(
132    const std::string& account_id) {
133  VLOG(1) << "OnRefreshTokenAvailable";
134
135  if (state_ == SESSION_RESTORE_NOT_STARTED)
136    return;
137
138  // TODO(fgorski): Once ProfileOAuth2TokenService supports multi-login, make
139  // sure to restore session cookies in the context of the correct account_id.
140
141  // Do not validate tokens for supervised users, as they don't actually have
142  // oauth2 token.
143  if (user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser()) {
144    VLOG(1) << "Logged in as supervised user, skip token validation.";
145    return;
146  }
147  // Only restore session cookies for the primary account in the profile.
148  if (GetPrimaryAccountId() == account_id) {
149    // Token is loaded. Undo the flagging before token loading.
150    user_manager::UserManager::Get()->SaveUserOAuthStatus(
151        account_id, user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
152    VerifySessionCookies();
153  }
154}
155
156ProfileOAuth2TokenService* OAuth2LoginManager::GetTokenService() {
157  return ProfileOAuth2TokenServiceFactory::GetForProfile(user_profile_);
158}
159
160const std::string& OAuth2LoginManager::GetPrimaryAccountId() {
161  SigninManagerBase* signin_manager =
162      SigninManagerFactory::GetForProfile(user_profile_);
163  return signin_manager->GetAuthenticatedAccountId();
164}
165
166void OAuth2LoginManager::StoreOAuth2Token() {
167  const std::string& primary_account_id = GetPrimaryAccountId();
168  if (primary_account_id.empty()) {
169    GetAccountIdOfRefreshToken(refresh_token_);
170    return;
171  }
172
173  OnGetUserEmailResponse(primary_account_id);
174}
175
176void OAuth2LoginManager::GetAccountIdOfRefreshToken(
177    const std::string& refresh_token) {
178  gaia::OAuthClientInfo client_info;
179  GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
180  client_info.client_id = gaia_urls->oauth2_chrome_client_id();
181  client_info.client_secret = gaia_urls->oauth2_chrome_client_secret();
182
183  account_id_fetcher_.reset(new gaia::GaiaOAuthClient(
184      auth_request_context_.get()));
185  account_id_fetcher_->RefreshToken(client_info, refresh_token,
186      std::vector<std::string>(1, kServiceScopeGetUserInfo), kMaxRetries,
187      this);
188}
189
190void OAuth2LoginManager::OnRefreshTokenResponse(
191    const std::string& access_token,
192    int expires_in_seconds) {
193  account_id_fetcher_->GetUserEmail(access_token, kMaxRetries, this);
194}
195
196void OAuth2LoginManager::OnGetUserEmailResponse(
197    const std::string& user_email)  {
198  DCHECK(!refresh_token_.empty());
199  account_id_fetcher_.reset();
200  std::string canonicalized = gaia::CanonicalizeEmail(user_email);
201  GetTokenService()->UpdateCredentials(canonicalized, refresh_token_);
202
203  FOR_EACH_OBSERVER(Observer, observer_list_,
204                    OnNewRefreshTokenAvaiable(user_profile_));
205}
206
207void OAuth2LoginManager::OnOAuthError() {
208  account_id_fetcher_.reset();
209  LOG(ERROR) << "Account id fetch failed!";
210  SetSessionRestoreState(OAuth2LoginManager::SESSION_RESTORE_FAILED);
211}
212
213void OAuth2LoginManager::OnNetworkError(int response_code) {
214  account_id_fetcher_.reset();
215  LOG(ERROR) << "Account id fetch failed! response_code=" << response_code;
216  SetSessionRestoreState(OAuth2LoginManager::SESSION_RESTORE_FAILED);
217}
218
219void OAuth2LoginManager::FetchOAuth2Tokens() {
220  DCHECK(auth_request_context_.get());
221  // If we have authenticated cookie jar, get OAuth1 token first, then fetch
222  // SID/LSID cookies through OAuthLogin call.
223  if (restore_strategy_ == RESTORE_FROM_COOKIE_JAR) {
224    SigninClient* signin_client =
225        ChromeSigninClientFactory::GetForProfile(user_profile_);
226    std::string signin_scoped_device_id =
227        signin_client->GetSigninScopedDeviceId();
228
229    oauth2_token_fetcher_.reset(
230        new OAuth2TokenFetcher(this, auth_request_context_.get()));
231    oauth2_token_fetcher_->StartExchangeFromCookies(std::string(),
232                                                    signin_scoped_device_id);
233  } else if (restore_strategy_ == RESTORE_FROM_AUTH_CODE) {
234    DCHECK(!auth_code_.empty());
235    oauth2_token_fetcher_.reset(
236        new OAuth2TokenFetcher(this,
237                               g_browser_process->system_request_context()));
238    oauth2_token_fetcher_->StartExchangeFromAuthCode(auth_code_);
239  } else {
240    NOTREACHED();
241    SetSessionRestoreState(OAuth2LoginManager::SESSION_RESTORE_FAILED);
242  }
243}
244
245void OAuth2LoginManager::OnOAuth2TokensAvailable(
246    const GaiaAuthConsumer::ClientOAuthResult& oauth2_tokens) {
247  VLOG(1) << "OAuth2 tokens fetched";
248  DCHECK(refresh_token_.empty());
249  refresh_token_.assign(oauth2_tokens.refresh_token);
250  oauthlogin_access_token_ = oauth2_tokens.access_token;
251  StoreOAuth2Token();
252}
253
254void OAuth2LoginManager::OnOAuth2TokensFetchFailed() {
255  LOG(ERROR) << "OAuth2 tokens fetch failed!";
256  RecordSessionRestoreOutcome(SESSION_RESTORE_TOKEN_FETCH_FAILED,
257                              SESSION_RESTORE_FAILED);
258}
259
260void OAuth2LoginManager::VerifySessionCookies() {
261  DCHECK(!login_verifier_.get());
262  login_verifier_.reset(
263      new OAuth2LoginVerifier(this,
264                              g_browser_process->system_request_context(),
265                              user_profile_->GetRequestContext(),
266                              oauthlogin_access_token_));
267
268  if (restore_strategy_ == RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN) {
269    login_verifier_->VerifyUserCookies(user_profile_);
270    return;
271  }
272
273  RestoreSessionCookies();
274}
275
276void OAuth2LoginManager::RestoreSessionCookies() {
277  SetSessionRestoreState(SESSION_RESTORE_IN_PROGRESS);
278  login_verifier_->VerifyProfileTokens(user_profile_);
279}
280
281void OAuth2LoginManager::Shutdown() {
282  GetTokenService()->RemoveObserver(this);
283  login_verifier_.reset();
284  oauth2_token_fetcher_.reset();
285}
286
287void OAuth2LoginManager::OnSessionMergeSuccess() {
288  VLOG(1) << "OAuth2 refresh and/or GAIA token verification succeeded.";
289  RecordSessionRestoreOutcome(SESSION_RESTORE_SUCCESS,
290                              SESSION_RESTORE_DONE);
291}
292
293void OAuth2LoginManager::OnSessionMergeFailure(bool connection_error) {
294  LOG(ERROR) << "OAuth2 refresh and GAIA token verification failed!"
295             << " connection_error: " << connection_error;
296  RecordSessionRestoreOutcome(SESSION_RESTORE_MERGE_SESSION_FAILED,
297                              connection_error ?
298                                  SESSION_RESTORE_CONNECTION_FAILED :
299                                  SESSION_RESTORE_FAILED);
300}
301
302void OAuth2LoginManager::OnListAccountsSuccess(const std::string& data) {
303  MergeVerificationOutcome outcome = POST_MERGE_SUCCESS;
304  // Let's analyze which accounts we see logged in here:
305  std::vector<std::pair<std::string, bool> > accounts;
306  gaia::ParseListAccountsData(data, &accounts);
307  std::string user_email = gaia::CanonicalizeEmail(GetPrimaryAccountId());
308  if (!accounts.empty()) {
309    bool found = false;
310    bool first = true;
311    for (std::vector<std::pair<std::string, bool> >::const_iterator iter =
312             accounts.begin();
313         iter != accounts.end(); ++iter) {
314      if (gaia::CanonicalizeEmail(iter->first) == user_email) {
315        found = iter->second;
316        break;
317      }
318
319      first = false;
320    }
321
322    if (!found)
323      outcome = POST_MERGE_MISSING_PRIMARY_ACCOUNT;
324    else if (!first)
325      outcome = POST_MERGE_PRIMARY_NOT_FIRST_ACCOUNT;
326
327  } else {
328    outcome = POST_MERGE_NO_ACCOUNTS;
329  }
330
331  bool is_pre_merge = (state_ == SESSION_RESTORE_PREPARING);
332  RecordCookiesCheckOutcome(is_pre_merge, outcome);
333  // If the primary account is missing during the initial cookie freshness
334  // check, try to restore GAIA session cookies form the OAuth2 tokens.
335  if (is_pre_merge) {
336    if (outcome != POST_MERGE_SUCCESS &&
337        outcome != POST_MERGE_PRIMARY_NOT_FIRST_ACCOUNT) {
338      RestoreSessionCookies();
339    } else {
340      // We are done with this account, it's GAIA cookies are legit.
341      RecordSessionRestoreOutcome(SESSION_RESTORE_NOT_NEEDED,
342                                  SESSION_RESTORE_DONE);
343    }
344  }
345}
346
347void OAuth2LoginManager::OnListAccountsFailure(bool connection_error) {
348  bool is_pre_merge = (state_ == SESSION_RESTORE_PREPARING);
349  RecordCookiesCheckOutcome(
350      is_pre_merge,
351      connection_error ? POST_MERGE_CONNECTION_FAILED :
352                         POST_MERGE_VERIFICATION_FAILED);
353  if (is_pre_merge) {
354    if (!connection_error) {
355      // If we failed to get account list, our cookies might be stale so we
356      // need to attempt to restore them.
357      RestoreSessionCookies();
358    } else {
359      RecordSessionRestoreOutcome(SESSION_RESTORE_LISTACCOUNTS_FAILED,
360                                  SESSION_RESTORE_CONNECTION_FAILED);
361    }
362  }
363}
364
365void OAuth2LoginManager::RecordSessionRestoreOutcome(
366    SessionRestoreOutcome outcome,
367    OAuth2LoginManager::SessionRestoreState state) {
368  UMA_HISTOGRAM_ENUMERATION("OAuth2Login.SessionRestore",
369                            outcome,
370                            SESSION_RESTORE_COUNT);
371  SetSessionRestoreState(state);
372}
373
374// static
375void OAuth2LoginManager::RecordCookiesCheckOutcome(
376    bool is_pre_merge,
377    MergeVerificationOutcome outcome) {
378  if (is_pre_merge) {
379    UMA_HISTOGRAM_ENUMERATION("OAuth2Login.PreMergeVerification",
380                              outcome,
381                              POST_MERGE_COUNT);
382  } else {
383    UMA_HISTOGRAM_ENUMERATION("OAuth2Login.PostMergeVerification",
384                              outcome,
385                              POST_MERGE_COUNT);
386  }
387}
388
389void OAuth2LoginManager::SetSessionRestoreState(
390    OAuth2LoginManager::SessionRestoreState state) {
391  if (state_ == state)
392    return;
393
394  state_ = state;
395  if (state == OAuth2LoginManager::SESSION_RESTORE_FAILED) {
396    UMA_HISTOGRAM_TIMES("OAuth2Login.SessionRestoreTimeToFailure",
397                        base::Time::Now() - session_restore_start_);
398  } else if (state == OAuth2LoginManager::SESSION_RESTORE_DONE) {
399    UMA_HISTOGRAM_TIMES("OAuth2Login.SessionRestoreTimeToSuccess",
400                        base::Time::Now() - session_restore_start_);
401  }
402
403  FOR_EACH_OBSERVER(Observer, observer_list_,
404                    OnSessionRestoreStateChanged(user_profile_, state_));
405}
406
407void OAuth2LoginManager::SetSessionRestoreStartForTesting(
408    const base::Time& time) {
409  session_restore_start_ = time;
410}
411
412}  // namespace chromeos
413