blob: ef40c12d0df98b2a267e268d802701f291994b20 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_ENDPOINT_FETCHER_ENDPOINT_FETCHER_H_
#define COMPONENTS_ENDPOINT_FETCHER_ENDPOINT_FETCHER_H_
#include <optional>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/json_sanitizer.h"
#include "services/network/public/mojom/fetch_api.mojom-forward.h"
#include "url/gurl.h"
namespace base {
class TimeDelta;
} // namespace base
namespace network {
class SimpleURLLoader;
class SharedURLLoaderFactory;
} // namespace network
namespace signin {
struct AccessTokenInfo;
class IdentityManager;
} // namespace signin
namespace version_info {
enum class Channel;
}
class GoogleServiceAuthError;
namespace endpoint_fetcher {
class EndpointFetcherTest;
enum class CredentialsMode {
kOmit = 0,
kInclude = 1,
};
enum class FetchErrorType {
kAuthError = 0,
kNetError = 1,
kResultParseError = 2,
};
enum class HttpMethod {
kUndefined = -1,
kGet = 0,
kPost = 1,
kDelete = 2,
};
enum AuthType {
// Unique identifier to access various server-side APIs Chrome uses.
CHROME_API_KEY,
// Authorization protocol to access an API based on account permissions.
OAUTH,
// No authentication used.
NO_AUTH
};
struct EndpointResponse {
std::string response;
int http_status_code{-1};
std::optional<FetchErrorType> error_type;
};
using EndpointFetcherCallback =
base::OnceCallback<void(std::unique_ptr<EndpointResponse>)>;
using UploadProgressCallback =
base::RepeatingCallback<void(uint64_t position, uint64_t total)>;
// TODO(crbug.com/284531303) EndpointFetcher would benefit from
// re-design/rethinking the APIs.
// EndpointFetcher calls an endpoint and returns
// the response. EndpointFetcher is not thread safe and it is up to the caller
// to wait until the callback function passed to Fetch() completes
// before invoking Fetch() again.
// Destroying an EndpointFetcher will result in the in-flight request being
// cancelled.
// EndpointFetcher performs authentication via the signed in user to
// Chrome.
// If the request times out an empty response will be returned. There will also
// be an error code indicating timeout once more detailed error messaging is
// added TODO(crbug.com/40640190).
class EndpointFetcher {
public:
// Parameters the client can configure for the request. This is part of our
// long term plan to move request parameters (e.g. URL, headers) to one
// centralized struct as adding additional parameters to the EndpointFetcher
// constructor does/will not scale. New parameters will be added here and
// existing parameters will be migrated (crbug.com/357567879).
class RequestParams {
public:
RequestParams(const HttpMethod& method,
const net::NetworkTrafficAnnotationTag& annotation_tag);
RequestParams(const EndpointFetcher::RequestParams& other);
~RequestParams();
struct Header {
std::string key;
std::string value;
Header(const std::string& key, const std::string& value) {
this->key = key;
this->value = value;
}
};
const AuthType& auth_type() const { return auth_type_; }
const GURL& url() const { return url_; }
const HttpMethod& http_method() const { return http_method_; }
const base::TimeDelta& timeout() const { return timeout_; }
const std::optional<std::string>& post_data() const { return post_data_; }
const std::vector<Header>& headers() const { return headers_; }
const std::vector<Header>& cors_exempt_headers() const {
return cors_exempt_headers_;
}
const net::NetworkTrafficAnnotationTag annotation_tag() const {
return annotation_tag_;
}
const std::string& content_type() const { return content_type_; }
std::optional<CredentialsMode> credentials_mode;
std::optional<int> max_retries;
std::optional<bool> set_site_for_cookies;
std::optional<UploadProgressCallback> upload_progress_callback;
// Authentication-specific parameters
std::optional<std::string> oauth_consumer_name;
signin::ScopeSet oauth_scopes;
std::optional<signin::ConsentLevel> consent_level;
std::optional<version_info::Channel> channel;
// Response behavior parameters
std::optional<bool> sanitize_response{true};
class Builder final {
public:
Builder(const HttpMethod& method,
const net::NetworkTrafficAnnotationTag& annotation_tag);
explicit Builder(const EndpointFetcher::RequestParams& other);
Builder(const Builder&) = delete;
Builder& operator=(const Builder&) = delete;
~Builder();
// Contains consistency DCHECKs.
RequestParams Build();
Builder& SetUrl(const GURL& url) {
request_params_->url_ = url;
return *this;
}
Builder& SetTimeout(const base::TimeDelta& timeout) {
request_params_->timeout_ = timeout;
return *this;
}
Builder& SetCredentialsMode(const CredentialsMode& mode) {
request_params_->credentials_mode = mode;
return *this;
}
Builder& SetMaxRetries(const int retries) {
request_params_->max_retries = retries;
return *this;
}
Builder& SetSetSiteForCookies(const bool should_set_site_for_cookies) {
request_params_->set_site_for_cookies = should_set_site_for_cookies;
return *this;
}
Builder& SetUploadProgressCallback(
const UploadProgressCallback callback) {
request_params_->upload_progress_callback = callback;
return *this;
}
Builder& SetPostData(const std::string& post_data) {
request_params_->post_data_ = post_data;
return *this;
}
Builder& SetHeaders(const std::vector<Header>& headers) {
request_params_->headers_ = std::move(headers);
return *this;
}
// Only use for legacy setting of Headers. Please use
// SetCorsExemptHeaders(const std::vector<Header... for any new usage of
// the EndpointFetcher.
Builder& SetHeaders(const std::vector<std::string>& headers) {
// The key and value alternate in this vector, so there is an
// expectation the vector is of even length.
DCHECK_EQ(headers.size() % 2, 0UL);
for (size_t i = 0; i + 1 < headers.size(); i += 2) {
request_params_->headers_.emplace_back(headers[i], headers[i + 1]);
}
return *this;
}
Builder& SetCorsExemptHeaders(
const std::vector<Header>& cors_exempt_headers) {
request_params_->cors_exempt_headers_ = std::move(cors_exempt_headers);
return *this;
}
// Only use for legacy setting of Cors Exempt Headers. Please use
// SetCorsExemptHeaders(const std::vector<Header... for any new usage of
// the EndpointFetcher.
Builder& SetCorsExemptHeaders(
const std::vector<std::string>& cors_exempt_headers) {
// The key and value alternate in this vector, so there is an
// expectation the vector is of even length.
DCHECK_EQ(cors_exempt_headers.size() % 2, 0UL);
for (size_t i = 0; i + 1 < cors_exempt_headers.size(); i += 2) {
request_params_->headers_.emplace_back(cors_exempt_headers[i],
cors_exempt_headers[i + 1]);
}
return *this;
}
Builder& SetAuthType(const AuthType auth_type) {
request_params_->auth_type_ = auth_type;
return *this;
}
Builder& SetContentType(const std::string& content_type) {
request_params_->content_type_ = content_type;
return *this;
}
// Authentication-specific builder methods
Builder& SetOauthConsumerName(const std::string& name) {
request_params_->oauth_consumer_name = name;
return *this;
}
Builder& SetOauthScopes(const signin::ScopeSet& scopes) {
request_params_->oauth_scopes = scopes;
return *this;
}
Builder& SetOauthScopes(const std::vector<std::string>& scopes_vector) {
for (const auto& scope : scopes_vector) {
request_params_->oauth_scopes.insert(scope);
}
return *this;
}
Builder& SetConsentLevel(signin::ConsentLevel level) {
request_params_->consent_level = level;
return *this;
}
Builder& SetChannel(version_info::Channel channel_val) {
request_params_->channel = channel_val;
return *this;
}
// Response behavior builder methods
Builder& SetSanitizeResponse(bool sanitize) {
request_params_->sanitize_response = sanitize;
return *this;
}
private:
std::unique_ptr<RequestParams> request_params_;
};
private:
friend class EndpointFetcher::RequestParams::Builder;
GURL url_;
HttpMethod http_method_{HttpMethod::kUndefined};
base::TimeDelta timeout_{base::Milliseconds(0)};
AuthType auth_type_{NO_AUTH};
std::string content_type_;
std::optional<std::string> post_data_;
std::vector<Header> headers_;
std::vector<Header> cors_exempt_headers_;
net::NetworkTrafficAnnotationTag annotation_tag_;
};
// Preferred constructor - forms identity_manager and url_loader_factory.
// OAUTH authentication is used for this constructor.
//
// Note: When using signin::ConsentLevel::kSignin, please also make sure that
// your `scopes` are correctly set in AccessTokenRestrictions, otherwise
// AccessTokenFetcher will assume the `scopes` requires full access and crash
// if user doesn't have full access (e.g. sign in but not sync).
// TODO(crbug.com/382343700): Add a DCHECK to enforce this in EndPointFetcher.
EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
signin::IdentityManager* identity_manager,
RequestParams request_params);
// Less preferred convenience constructor for OAuth authenticated requests.
//
// This constructor internally configures `RequestParams` for OAuth
// authentication using the provided details.
//
// For new code, prefer constructing `RequestParams` directly and using the
// primary `EndpointFetcher(url_loader_factory, identity_manager,
// request_params)` constructor.
EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const std::string& oauth_consumer_name,
const GURL& url,
const std::string& http_method,
const std::string& content_type,
const std::vector<std::string>& scopes,
const base::TimeDelta& timeout,
const std::string& post_data,
const net::NetworkTrafficAnnotationTag& annotation_tag,
signin::IdentityManager* identity_manager,
signin::ConsentLevel consent_level);
// Less preferred convenience constructor for Chrome API Key authenticated
// requests.
//
// This constructor configures `RequestParams` for Chrome API Key
// authentication using the provided `channel` and other network parameters.
// It may override some settings from the passed `request_params` argument.
//
// For new code, prefer constructing `RequestParams` directly (including
// setting `AuthType::CHROME_API_KEY` and `channel`) and using the primary
// `EndpointFetcher(url_loader_factory, identity_manager, request_params)`
// constructor (with `identity_manager` as nullptr for API key auth).
EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const GURL& url,
const std::string& content_type,
const base::TimeDelta& timeout,
const std::string& post_data,
const std::vector<std::string>& headers,
const std::vector<std::string>& cors_exempt_headers,
version_info::Channel channel,
const RequestParams request_params);
// Less preferred convenience constructor for requests requiring no
// authentication.
//
// This constructor configures `RequestParams` for `NO_AUTH`.
//
// For new code, prefer constructing `RequestParams` directly (setting
// `AuthType::NO_AUTH`) and using the primary
// `EndpointFetcher(url_loader_factory, identity_manager, request_params)`
// constructor (with `identity_manager` as nullptr).
EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const GURL& url,
const net::NetworkTrafficAnnotationTag& annotation_tag);
// Used internally. Can be used if caller constructs their own
// url_loader_factory and identity_manager.
EndpointFetcher(
const std::string& oauth_consumer_name,
const GURL& url,
const std::string& http_method,
const std::string& content_type,
const std::vector<std::string>& scopes,
const base::TimeDelta& timeout,
const std::string& post_data,
const net::NetworkTrafficAnnotationTag& annotation_tag,
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
signin::IdentityManager* identity_manager,
signin::ConsentLevel consent_level);
EndpointFetcher(const EndpointFetcher& endpoint_fetcher) = delete;
EndpointFetcher& operator=(const EndpointFetcher& endpoint_fetcher) = delete;
virtual ~EndpointFetcher();
// TODO(crbug.com/40642723) enable cancellation support
virtual void Fetch(EndpointFetcherCallback callback);
// Deprecated, use Fetch().
virtual void PerformRequest(EndpointFetcherCallback endpoint_fetcher_callback,
const char* key);
std::string GetUrlForTesting();
protected:
// Used for Mock only. see MockEndpointFetcher class.
explicit EndpointFetcher(
const net::NetworkTrafficAnnotationTag& annotation_tag);
private:
friend class EndpointFetcherTest;
void OnAuthTokenFetched(EndpointFetcherCallback callback,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
// Private helper, replaces PerformRequest.
void PerformHttpRequest(const char* auth_token_key,
EndpointFetcherCallback endpoint_fetcher_callback);
void OnResponseFetched(EndpointFetcherCallback callback,
std::unique_ptr<std::string> response_body);
void OnSanitizationResult(std::unique_ptr<EndpointResponse> response,
EndpointFetcherCallback endpoint_fetcher_callback,
data_decoder::JsonSanitizer::Result result);
network::mojom::CredentialsMode GetCredentialsMode() const;
int GetMaxRetries() const;
bool GetSetSiteForCookies() const;
UploadProgressCallback GetUploadProgressCallback() const;
// Members set in constructor
const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// `identity_manager_` can be null if it is not needed for authentication (in
// this case, callers should invoke `PerformRequest` directly).
const raw_ptr<signin::IdentityManager> identity_manager_;
// The complete definition of the specific network request to be performed.
// Contains authentication details and response handling preferences.
const RequestParams request_params_;
// Members set in Fetch
std::unique_ptr<const signin::PrimaryAccountAccessTokenFetcher>
access_token_fetcher_;
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
base::WeakPtrFactory<EndpointFetcher> weak_ptr_factory_{this};
};
} // namespace endpoint_fetcher
#endif // COMPONENTS_ENDPOINT_FETCHER_ENDPOINT_FETCHER_H_