| // 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/check_op.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_observation_.Observe(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_observation_.IsObservingSource(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; |
| |
| DCHECK(identity_manager_observation_.IsObservingSource(identity_manager_)); |
| identity_manager_observation_.Reset(); |
| |
| 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 |