summaryrefslogtreecommitdiff
path: root/chromium/components/signin/public/identity_manager/primary_account_access_token_fetcher.cc
blob: 56d1b5c2e89af8658ae8c8c28c22a6c30bcbdfe4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2017 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 "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"

#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "google_apis/gaia/core_account_id.h"
#include "google_apis/gaia/google_service_auth_error.h"

namespace signin {

PrimaryAccountAccessTokenFetcher::PrimaryAccountAccessTokenFetcher(
    const std::string& oauth_consumer_name,
    IdentityManager* identity_manager,
    const ScopeSet& scopes,
    AccessTokenFetcher::TokenCallback callback,
    Mode mode,
    ConsentLevel consent)
    : oauth_consumer_name_(oauth_consumer_name),
      identity_manager_(identity_manager),
      scopes_(scopes),
      callback_(std::move(callback)),
      access_token_retried_(false),
      mode_(mode),
      consent_(consent) {
  if (mode_ == Mode::kImmediate || AreCredentialsAvailable()) {
    StartAccessTokenRequest();
    return;
  }

  // Start observing the IdentityManager. This observer will be removed either
  // when credentials are obtained and an access token request is started or
  // when this object is destroyed.
  identity_manager_observer_.Add(identity_manager_);
}

PrimaryAccountAccessTokenFetcher::~PrimaryAccountAccessTokenFetcher() = default;

CoreAccountId PrimaryAccountAccessTokenFetcher::GetAccountId() const {
  return identity_manager_->GetPrimaryAccountId(consent_);
}

bool PrimaryAccountAccessTokenFetcher::AreCredentialsAvailable() const {
  DCHECK_EQ(Mode::kWaitUntilAvailable, mode_);

  return identity_manager_->HasAccountWithRefreshToken(GetAccountId());
}

void PrimaryAccountAccessTokenFetcher::StartAccessTokenRequest() {
  DCHECK(mode_ == Mode::kImmediate || AreCredentialsAvailable());

  // By the time of starting an access token request, we should no longer be
  // listening for signin-related events.
  DCHECK(!identity_manager_observer_.IsObserving(identity_manager_));

  // Note: We might get here even in cases where we know that there's no refresh
  // token. We're requesting an access token anyway, so that the token service
  // will generate an appropriate error code that we can return to the client.
  DCHECK(!access_token_fetcher_);

  // NOTE: This class does not utilize AccessTokenFetcher in its
  // |kWaitUntilRefreshTokenAvailable| mode because the PAATF semantics specify
  // that when used in *its* |kWaitUntilAvailable| mode, the access token
  // request should be started when the account is primary AND has a refresh
  // token available. AccessTokenFetcher used in
  // |kWaitUntilRefreshTokenAvailable| mode would guarantee only the latter.
  access_token_fetcher_ = identity_manager_->CreateAccessTokenFetcherForAccount(
      GetAccountId(), oauth_consumer_name_, scopes_,
      base::BindOnce(
          &PrimaryAccountAccessTokenFetcher::OnAccessTokenFetchComplete,
          base::Unretained(this)),
      AccessTokenFetcher::Mode::kImmediate);
}

void PrimaryAccountAccessTokenFetcher::OnPrimaryAccountSet(
    const CoreAccountInfo& primary_account_info) {
  // When sync consent is not required the signin is handled in
  // OnUnconsentedPrimaryAccountChanged() below.
  if (consent_ == ConsentLevel::kNotRequired)
    return;
  DCHECK(!primary_account_info.account_id.empty());
  ProcessSigninStateChange();
}

void PrimaryAccountAccessTokenFetcher::OnUnconsentedPrimaryAccountChanged(
    const CoreAccountInfo& primary_account_info) {
  // This method is called after both SetPrimaryAccount and
  // SetUnconsentedPrimaryAccount.
  if (consent_ == ConsentLevel::kSync)
    return;
  // We're only interested when the account is set.
  if (primary_account_info.account_id.empty())
    return;
  ProcessSigninStateChange();
}

void PrimaryAccountAccessTokenFetcher::OnRefreshTokenUpdatedForAccount(
    const CoreAccountInfo& account_info) {
  ProcessSigninStateChange();
}

void PrimaryAccountAccessTokenFetcher::ProcessSigninStateChange() {
  DCHECK_EQ(Mode::kWaitUntilAvailable, mode_);

  if (!AreCredentialsAvailable())
    return;

  identity_manager_observer_.Remove(identity_manager_);

  StartAccessTokenRequest();
}

void PrimaryAccountAccessTokenFetcher::OnAccessTokenFetchComplete(
    GoogleServiceAuthError error,
    AccessTokenInfo access_token_info) {
  access_token_fetcher_.reset();

  // There is a special case for Android that RefreshTokenIsAvailable and
  // StartRequest are called to pre-fetch the account image and name before
  // sign-in. In that case, our ongoing access token request gets cancelled.
  // Moreover, OnRefreshTokenAvailable might happen after startup when the
  // credentials are changed/updated.
  // To handle these cases, we retry a canceled request once.
  // However, a request may also get cancelled for legitimate reasons, e.g.
  // because the user signed out. In those cases, there's no point in retrying,
  // so only retry if there (still) is a valid refresh token.
  // NOTE: Maybe we should retry for all transient errors here, so that clients
  // don't have to.
  if (mode_ == Mode::kWaitUntilAvailable && !access_token_retried_ &&
      error.state() == GoogleServiceAuthError::State::REQUEST_CANCELED &&
      AreCredentialsAvailable()) {
    access_token_retried_ = true;
    StartAccessTokenRequest();
    return;
  }

  // Per the contract of this class, it is allowed for consumers to delete this
  // object from within the callback that is run below. Hence, it is not safe to
  // add any code below this call.
  std::move(callback_).Run(std::move(error), std::move(access_token_info));
}

}  // namespace signin