Commit 5d525d58 authored by Lambros Lambrou's avatar Lambros Lambrou Committed by Commit Bot

Support offline refresh tokens in OAuthTokenExchanger.

This adds support for requesting offline access for the new token (via
the 'offline' field in the gRPC request).

This also suppports providing the new OAuth refresh token (as well as
the access token) to the callback.

This functionality will be used for updating the host's config, in case
the stored refresh token has insufficient scopes. This will be
provided by a separate class in an upcoming CL.

The OAuthTokenGetter is not affected - it will always use "online" mode
to transparently deliver access-tokens with required scopes.

Bug: 954427
Change-Id: I6a09677b3a8cc0e9a61e98dc4bb084031c77cc9c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1625955
Commit-Queue: Joe Downing <joedow@chromium.org>
Reviewed-by: default avatarJoe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662489}
parent 50186cc0
......@@ -52,6 +52,7 @@ class OAuthTokenExchanger::DirectoryServiceClient {
~DirectoryServiceClient();
void UpdateRobotToken(const std::string& access_token,
bool offline,
UpdateRobotTokenCallback callback);
private:
......@@ -72,11 +73,12 @@ OAuthTokenExchanger::DirectoryServiceClient::~DirectoryServiceClient() =
void OAuthTokenExchanger::DirectoryServiceClient::UpdateRobotToken(
const std::string& access_token,
bool offline,
UpdateRobotTokenCallback callback) {
auto update_robot_token_request = apis::v1::UpdateRobotTokenRequest();
update_robot_token_request.set_client_id(
google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING_HOST));
update_robot_token_request.set_offline(false);
update_robot_token_request.set_offline(offline);
auto async_request = CreateGrpcAsyncUnaryRequest(
base::BindOnce(&RemotingDirectoryService::Stub::AsyncUpdateRobotToken,
......@@ -111,7 +113,8 @@ void OAuthTokenExchanger::ExchangeToken(const std::string& access_token,
}
// Return the original token, as it already has required scopes.
NotifyCallbacks(OAuthTokenGetter::SUCCESS, oauth_access_token_);
NotifyCallbacks(OAuthTokenGetter::SUCCESS, std::string() /* refresh_token */,
oauth_access_token_);
}
void OAuthTokenExchanger::OnGetTokensResponse(const std::string& refresh_token,
......@@ -119,14 +122,14 @@ void OAuthTokenExchanger::OnGetTokensResponse(const std::string& refresh_token,
int expires_in_seconds) {
// |expires_in_seconds| is unused - the exchanged token is assumed to be
// valid for at least as long as the original access token.
NotifyCallbacks(OAuthTokenGetter::SUCCESS, access_token);
NotifyCallbacks(OAuthTokenGetter::SUCCESS, refresh_token, access_token);
}
void OAuthTokenExchanger::OnGetTokenInfoResponse(
std::unique_ptr<base::DictionaryValue> token_info) {
base::Value* scopes_value = token_info->FindKey(TOKENINFO_SCOPE_KEY);
if (!scopes_value || !scopes_value->is_string()) {
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string());
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
return;
}
std::string scopes = scopes_value->GetString();
......@@ -137,34 +140,37 @@ void OAuthTokenExchanger::OnGetTokenInfoResponse(
if (need_token_exchange_.value()) {
RequestNewToken();
} else {
NotifyCallbacks(OAuthTokenGetter::SUCCESS, oauth_access_token_);
NotifyCallbacks(OAuthTokenGetter::SUCCESS,
std::string() /* refresh_token */, oauth_access_token_);
}
}
void OAuthTokenExchanger::OnOAuthError() {
LOG(ERROR) << "OAuth error.";
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string());
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
}
void OAuthTokenExchanger::OnNetworkError(int response_code) {
LOG(ERROR) << "Network error: " << response_code;
NotifyCallbacks(OAuthTokenGetter::NETWORK_ERROR, std::string());
NotifyCallbacks(OAuthTokenGetter::NETWORK_ERROR, std::string(),
std::string());
}
void OAuthTokenExchanger::NotifyCallbacks(OAuthTokenGetter::Status status,
const std::string& refresh_token,
const std::string& access_token) {
// Protect against recursion by moving the callbacks into a temporary list.
base::queue<TokenCallback> callbacks;
callbacks.swap(pending_callbacks_);
while (!callbacks.empty()) {
std::move(callbacks.front()).Run(status, access_token);
std::move(callbacks.front()).Run(status, refresh_token, access_token);
callbacks.pop();
}
}
void OAuthTokenExchanger::RequestNewToken() {
directory_service_client_->UpdateRobotToken(
oauth_access_token_,
oauth_access_token_, offline_mode_,
base::BindOnce(&OAuthTokenExchanger::OnRobotTokenResponse,
base::Unretained(this)));
}
......@@ -175,13 +181,13 @@ void OAuthTokenExchanger::OnRobotTokenResponse(
if (!status.ok()) {
LOG(ERROR) << "Received error code: " << status.error_code()
<< ", message: " << status.error_message();
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string());
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
return;
}
if (!response.has_auth_code()) {
LOG(ERROR) << "Received response without auth_code.";
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string());
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
return;
}
......
......@@ -31,6 +31,7 @@ class UpdateRobotTokenResponse;
class OAuthTokenExchanger : public gaia::GaiaOAuthClient::Delegate {
public:
typedef base::OnceCallback<void(OAuthTokenGetter::Status status,
const std::string& refresh_token,
const std::string& access_token)>
TokenCallback;
......@@ -38,6 +39,18 @@ class OAuthTokenExchanger : public gaia::GaiaOAuthClient::Delegate {
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
~OAuthTokenExchanger() override;
void set_offline_mode(bool offline_mode) { offline_mode_ = offline_mode; }
// |access_token| should be an OAuth access token derived from the refresh
// token from the host's config. This will test the token's scopes to see if
// an exchange is needed. The test only happens once - the result is
// cached for subsequent calls.
// If exchange occurred, the new access and refresh tokens (fetched
// from OAuth "token" endpoint) are returned to |on_new_token| callback.
// If exchange did not occur, the provided |access_token| and an empty string
// for the refresh token are returned.
// A caller can determine if exchange occurred by comparing the returned
// access token with the input |access_token| for equality.
void ExchangeToken(const std::string& access_token,
TokenCallback on_new_token);
......@@ -54,6 +67,7 @@ class OAuthTokenExchanger : public gaia::GaiaOAuthClient::Delegate {
class DirectoryServiceClient;
void NotifyCallbacks(OAuthTokenGetter::Status status,
const std::string& refresh_token,
const std::string& access_token);
void RequestNewToken();
void OnRobotTokenResponse(const grpc::Status& status,
......@@ -63,6 +77,8 @@ class OAuthTokenExchanger : public gaia::GaiaOAuthClient::Delegate {
base::queue<TokenCallback> pending_callbacks_;
std::string oauth_access_token_;
bool offline_mode_ = false;
// True if the OAuth refresh token is lacking required scopes and the
// token-exchange service is needed to provide a new access-token.
// False if the refresh token is up-to-date with required scopes.
......
......@@ -292,6 +292,7 @@ void OAuthTokenGetterImpl::ExchangeAccessToken() {
void OAuthTokenGetterImpl::OnExchangeTokenResponse(
Status status,
const std::string& refresh_token,
const std::string& access_token) {
oauth_access_token_ = access_token;
switch (status) {
......
......@@ -68,7 +68,9 @@ class OAuthTokenGetterImpl : public OAuthTokenGetter,
const std::string& refresh_token);
void GetOauthTokensFromAuthCode();
void RefreshAccessToken();
void OnExchangeTokenResponse(Status status, const std::string& access_token);
void OnExchangeTokenResponse(Status status,
const std::string& refresh_token,
const std::string& access_token);
// Fetches the OAuth scopes for |oauth_access_token_|. If it is missing the
// new scopes required by FTL signaling, it exchanges it for a new access
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment