blob: 4b3a81aa804c72baf08155f07a0b0d97273e7b38 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CONFIG_PROVIDER_H_
#define CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CONFIG_PROVIDER_H_
#include <memory>
#include <optional>
#include <string>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "chrome/browser/ip_protection/ip_protection_config_http.h"
#include "chrome/browser/ip_protection/ip_protection_config_provider_factory.h"
#include "components/privacy_sandbox/tracking_protection_settings.h"
#include "components/privacy_sandbox/tracking_protection_settings_observer.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/network_context.mojom.h"
class Profile;
namespace quiche {
class BlindSignAuthInterface;
class BlindSignAuth;
struct BlindSignToken;
} // namespace quiche
// The result of a fetch of tokens from the IP Protection auth token server.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. Keep this in sync with
// IpProtectionTokenBatchRequestResult in enums.xml.
enum class IpProtectionTryGetAuthTokensResult {
// The request was successful and resulted in new tokens.
kSuccess = 0,
// No primary account is set.
kFailedNoAccount = 1,
// Chrome determined the primary account is not eligible.
kFailedNotEligible = 2,
// There was a failure fetching an OAuth token for the primary account.
// Deprecated in favor of `kFailedOAuthToken{Transient,Persistent}`.
kFailedOAuthTokenDeprecated = 3,
// There was a failure in BSA with the given status code.
kFailedBSA400 = 4,
kFailedBSA401 = 5,
kFailedBSA403 = 6,
// Any other issue calling BSA.
kFailedBSAOther = 7,
// There was a transient failure fetching an OAuth token for the primary
// account.
kFailedOAuthTokenTransient = 8,
// There was a persistent failure fetching an OAuth token for the primary
// account.
kFailedOAuthTokenPersistent = 9,
// The attempt to request tokens failed because IP Protection was disabled by
// the user.
kFailedDisabledByUser = 10,
kMaxValue = kFailedDisabledByUser,
};
// Fetches IP protection tokens on demand for the network service.
//
// This class handles both requesting OAuth2 tokens for the signed-in user, and
// fetching blind-signed auth tokens for that user. It may only be used on the
// UI thread.
class IpProtectionConfigProvider
: public KeyedService,
public network::mojom::IpProtectionConfigGetter,
public signin::IdentityManager::Observer,
public privacy_sandbox::TrackingProtectionSettingsObserver {
public:
IpProtectionConfigProvider(
signin::IdentityManager* identity_manager,
privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
PrefService* pref_service,
Profile* profile);
~IpProtectionConfigProvider() override;
// Get a batch of blind-signed auth tokens.
//
// It is forbidden for two calls to this method to be outstanding at the same
// time.
void TryGetAuthTokens(uint32_t batch_size,
network::mojom::IpProtectionProxyLayer proxy_layer,
TryGetAuthTokensCallback callback) override;
// Get the list of IP Protection proxies.
void GetProxyList(GetProxyListCallback callback) override;
// KeyedService:
void Shutdown() override;
static IpProtectionConfigProvider* Get(Profile* profile);
static bool CanIpProtectionBeEnabled();
bool IsIpProtectionEnabled();
// Add bidirectional pipes to a new network service.
void AddNetworkService(
mojo::PendingReceiver<network::mojom::IpProtectionConfigGetter>
pending_receiver,
mojo::PendingRemote<network::mojom::IpProtectionProxyDelegate>
pending_remote);
mojo::ReceiverSet<network::mojom::IpProtectionConfigGetter>&
receivers_for_testing() {
return receivers_;
}
mojo::ReceiverId receiver_id_for_testing() {
return receiver_id_for_testing_;
}
network::mojom::IpProtectionProxyDelegate* last_remote_for_testing() {
return remotes_.Get(remote_id_for_testing_);
}
// Like `SetUp()`, but providing values for each of the member variables.
void SetUpForTesting(
std::unique_ptr<IpProtectionConfigHttp> ip_protection_config_http_,
quiche::BlindSignAuthInterface* bsa);
// Base time deltas for calculating `try_again_after`.
static constexpr base::TimeDelta kNotEligibleBackoff = base::Days(1);
static constexpr base::TimeDelta kTransientBackoff = base::Seconds(5);
static constexpr base::TimeDelta kBugBackoff = base::Minutes(10);
private:
friend class IpProtectionConfigProviderTest;
FRIEND_TEST_ALL_PREFIXES(IpProtectionConfigProviderTest, CalculateBackoff);
FRIEND_TEST_ALL_PREFIXES(IpProtectionConfigProviderBrowserTest,
BackoffTimeResetAfterProfileAvailabilityChange);
FRIEND_TEST_ALL_PREFIXES(IpProtectionConfigProviderUserSettingBrowserTest,
OnIpProtectionEnabledChanged);
// Set up `ip_protection_config_http_` and `bsa_`, if not already initialized.
// This accomplishes lazy loading of these components to break dependency
// loops in browser startup.
void SetUp();
// Creating a generic callback in order for `RequestOAuthToken()` to work for
// `TryGetAuthTokens()` and `GetProxyList()`.
using RequestOAuthTokenCallback =
base::OnceCallback<void(GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info)>;
// Calls the IdentityManager asynchronously to request the OAuth token for the
// logged in user. This method must only be called when
// `CanRequestOAuthToken()` returns true.
void RequestOAuthToken(RequestOAuthTokenCallback callback);
bool CanRequestOAuthToken();
void OnRequestOAuthTokenCompleted(
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
oauth_token_fetcher,
RequestOAuthTokenCallback callback,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
void OnRequestOAuthTokenCompletedForTryGetAuthTokens(
uint32_t batch_size,
network::mojom::IpProtectionProxyLayer proxy_layer,
TryGetAuthTokensCallback callback,
base::TimeTicks oauth_token_fetch_start_time,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
void OnRequestOAuthTokenCompletedForGetProxyConfig(
GetProxyListCallback callback,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
// Wrapping `ip_protection_config_http_->GetProxyConfig()` method
// to enable OAuth Token inclusion in the GetProxyConfig API call to Phosphor.
void CallGetProxyConfig(GetProxyListCallback callback,
std::optional<std::string> oauth_token);
void OnGetProxyConfigCompleted(
GetProxyListCallback callback,
base::expected<ip_protection::GetProxyConfigResponse, std::string>
response);
// `FetchBlindSignedToken()` calls into the `quiche::BlindSignAuth` library to
// request a blind-signed auth token for use at the IP Protection proxies.
void FetchBlindSignedToken(signin::AccessTokenInfo access_token_info,
uint32_t batch_size,
network::mojom::IpProtectionProxyLayer proxy_layer,
TryGetAuthTokensCallback callback);
void OnFetchBlindSignedTokenCompleted(
base::TimeTicks bsa_get_tokens_start_time,
TryGetAuthTokensCallback callback,
absl::StatusOr<absl::Span<quiche::BlindSignToken>>);
static network::mojom::BlindSignedAuthTokenPtr CreateBlindSignedAuthToken(
quiche::BlindSignToken bsa_token);
void ClearOAuthTokenProblemBackoff();
// The object used to get an OAuth token. `identity_manager_` will be set to
// nullptr after `Shutdown()` is called, but will otherwise be non-null.
raw_ptr<signin::IdentityManager> identity_manager_;
// Used to retrieve whether the user has enabled IP protection via settings.
// `tracking_protection_settings_` will be set to nullptr after `Shutdown()`
// is called, but will otherwise be non-null.
raw_ptr<privacy_sandbox::TrackingProtectionSettings>
tracking_protection_settings_;
// Used to request the state of the IP Protection user setting. Will be set to
// nullptr after `Shutdown()` is called.
raw_ptr<PrefService> pref_service_;
// The `Profile` object associated with this
// `IpProtectionConfigProvider()`. Will be reset to nullptr after
// `Shutdown()` is called.
// NOTE: If this is used in any `GetForProfile()` call, ensure that there is a
// corresponding dependency (if needed) registered in the factory class.
raw_ptr<Profile> profile_;
// Finish a call to `TryGetAuthTokens()` by recording the result and invoking
// its callback.
void TryGetAuthTokensComplete(
std::optional<std::vector<network::mojom::BlindSignedAuthTokenPtr>>
bsa_tokens,
TryGetAuthTokensCallback callback,
IpProtectionTryGetAuthTokensResult result);
// Calculates the backoff time for the given result, based on
// `last_try_get_auth_tokens_..` fields, and updates those fields.
std::optional<base::TimeDelta> CalculateBackoff(
IpProtectionTryGetAuthTokensResult result);
// Instruct the `IpProtectionConfigCache()`(s) in the Network Service to
// ignore any previously sent `try_again_after` times.
void InvalidateNetworkContextTryAgainAfterTime();
// IdentityManager::Observer:
void OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event) override;
void OnErrorStateOfRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info,
const GoogleServiceAuthError& error) override;
// TrackingProtectionSettingsObserver:
void OnIpProtectionEnabledChanged() override;
// The BlindSignAuth implementation used to fetch blind-signed auth tokens. A
// raw pointer to `url_loader_factory_` gets passed to
// `ip_protection_config_http_`, so we ensure it stays alive by storing its
// scoped_refptr here.
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
std::unique_ptr<IpProtectionConfigHttp> ip_protection_config_http_;
std::unique_ptr<quiche::BlindSignAuth> blind_sign_auth_;
// For testing, BlindSignAuth is accessed via its interface. In production,
// this is the same pointer as `blind_sign_auth_`.
raw_ptr<quiche::BlindSignAuthInterface> bsa_ = nullptr;
// Whether `Shutdown()` has been called.
bool is_shutting_down_ = false;
// The result of the last call to `TryGetAuthTokens()`, and the
// backoff applied to `try_again_after`. `last_try_get_auth_tokens_backoff_`
// will be set to `base::TimeDelta::Max()` if no further attempts to get
// tokens should be made. These will be updated by calls from any receiver
// (so, from either the main profile or an associated incognito mode profile).
IpProtectionTryGetAuthTokensResult last_try_get_auth_tokens_result_ =
IpProtectionTryGetAuthTokensResult::kSuccess;
std::optional<base::TimeDelta> last_try_get_auth_tokens_backoff_;
// The `mojo::Receiver` objects allowing the network service to call methods
// on `this`.
//
// At any given time there should only be two receivers, one for the main
// profile and another one if an associated incognito window is opened.
// If one of the corresponding Network Contexts restarts, the
// corresponding receiver will automatically be removed and a new one
// bound as part of the Network Context initialization flow.
mojo::ReceiverSet<network::mojom::IpProtectionConfigGetter> receivers_;
// Similar to `receivers_`, but containing remotes for all existing
// IpProtectionProxyDelegates.
mojo::RemoteSet<network::mojom::IpProtectionProxyDelegate> remotes_;
// The `mojo::ReceiverId` of the most recently added `mojo::Receiver`, for
// testing.
mojo::ReceiverId receiver_id_for_testing_;
// The `mojo::RemoteSetElementId` of the most recently added `mojo::Remote`,
// for testing.
mojo::RemoteSetElementId remote_id_for_testing_;
// This must be the last member in this class.
base::WeakPtrFactory<IpProtectionConfigProvider> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CONFIG_PROVIDER_H_