Commit cb0e4af9 authored by William Lin's avatar William Lin Committed by Commit Bot

Split token cache from IdentityAPI and add tests

Implementing a subset matching cache will require a more complex cache
for the IdentityAPI class. At the moment, the existing cache is
contained inside the IdentityAPI class.

This CL separates the two, so all of the caching logic for IdentityAPI
is in its own class. Additionally, unit tests have been added to test
the basic functionality of this cache.

Bug: 1100535
Change-Id: I39f5357d3ce87ae4d958aa2c8585cc2deec624be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2343318Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Reviewed-by: default avatarAlex Ilin <alexilin@chromium.org>
Commit-Queue: William Lin <williamlin@google.com>
Cr-Commit-Position: refs/heads/master@{#797527}
parent e2fc2086
...@@ -197,6 +197,8 @@ static_library("extensions") { ...@@ -197,6 +197,8 @@ static_library("extensions") {
"api/identity/identity_private_api.h", "api/identity/identity_private_api.h",
"api/identity/identity_remove_cached_auth_token_function.cc", "api/identity/identity_remove_cached_auth_token_function.cc",
"api/identity/identity_remove_cached_auth_token_function.h", "api/identity/identity_remove_cached_auth_token_function.h",
"api/identity/identity_token_cache.cc",
"api/identity/identity_token_cache.h",
"api/identity/web_auth_flow.cc", "api/identity/web_auth_flow.cc",
"api/identity/web_auth_flow.h", "api/identity/web_auth_flow.h",
"api/idltest/idltest_api.cc", "api/idltest/idltest_api.cc",
......
...@@ -6,9 +6,7 @@ ...@@ -6,9 +6,7 @@
#include <stddef.h> #include <stddef.h>
#include <algorithm>
#include <memory> #include <memory>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
...@@ -23,7 +21,6 @@ ...@@ -23,7 +21,6 @@
#include "chrome/browser/app_mode/app_mode_utils.h" #include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/identity/identity_constants.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/account_consistency_mode_manager.h" #include "chrome/browser/signin/account_consistency_mode_manager.h"
...@@ -47,99 +44,6 @@ namespace { ...@@ -47,99 +44,6 @@ namespace {
const char kIdentityGaiaIdPref[] = "identity_gaia_id"; const char kIdentityGaiaIdPref[] = "identity_gaia_id";
} }
IdentityTokenCacheValue::IdentityTokenCacheValue() = default;
IdentityTokenCacheValue::IdentityTokenCacheValue(
const IdentityTokenCacheValue& other) = default;
IdentityTokenCacheValue::~IdentityTokenCacheValue() = default;
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateIssueAdvice(
const IssueAdviceInfo& issue_advice) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_ADVICE;
cache_value.issue_advice_ = issue_advice;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsent(
const RemoteConsentResolutionData& resolution_data) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT;
cache_value.resolution_data_ = resolution_data;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsentApproved(
const std::string& consent_result) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT_APPROVED;
cache_value.consent_result_ = consent_result;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateToken(
const std::string& token,
base::TimeDelta time_to_live) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_TOKEN;
cache_value.token_ = token;
// Remove 20 minutes from the ttl so cached tokens will have some time
// to live any time they are returned.
time_to_live -= base::TimeDelta::FromMinutes(20);
base::TimeDelta zero_delta;
if (time_to_live < zero_delta)
time_to_live = zero_delta;
cache_value.expiration_time_ = base::Time::Now() + time_to_live;
return cache_value;
}
IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
const {
if (is_expired())
return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
else
return status_;
}
const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
return issue_advice_;
}
const RemoteConsentResolutionData& IdentityTokenCacheValue::resolution_data()
const {
return resolution_data_;
}
const std::string& IdentityTokenCacheValue::consent_result() const {
return consent_result_;
}
const std::string& IdentityTokenCacheValue::token() const { return token_; }
bool IdentityTokenCacheValue::is_expired() const {
return status_ == CACHE_STATUS_NOTFOUND ||
expiration_time_ < base::Time::Now();
}
const base::Time& IdentityTokenCacheValue::expiration_time() const {
return expiration_time_;
}
IdentityAPI::IdentityAPI(content::BrowserContext* context) IdentityAPI::IdentityAPI(content::BrowserContext* context)
: IdentityAPI(Profile::FromBrowserContext(context), : IdentityAPI(Profile::FromBrowserContext(context),
IdentityManagerFactory::GetForProfile( IdentityManagerFactory::GetForProfile(
...@@ -151,37 +55,8 @@ IdentityAPI::~IdentityAPI() {} ...@@ -151,37 +55,8 @@ IdentityAPI::~IdentityAPI() {}
IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; } IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; }
void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key, IdentityTokenCache* IdentityAPI::token_cache() {
const IdentityTokenCacheValue& token_data) { return &token_cache_;
auto it = token_cache_.find(key);
if (it != token_cache_.end() && it->second.status() <= token_data.status())
token_cache_.erase(it);
token_cache_.insert(std::make_pair(key, token_data));
}
void IdentityAPI::EraseCachedToken(const std::string& extension_id,
const std::string& token) {
CachedTokens::iterator it;
for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
if (it->first.extension_id == extension_id &&
it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
it->second.token() == token) {
token_cache_.erase(it);
break;
}
}
}
void IdentityAPI::EraseAllCachedTokens() { token_cache_.clear(); }
const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
const ExtensionTokenKey& key) {
return token_cache_[key];
}
const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
return token_cache_;
} }
void IdentityAPI::SetGaiaIdForExtension(const std::string& extension_id, void IdentityAPI::SetGaiaIdForExtension(const std::string& extension_id,
......
...@@ -5,11 +5,8 @@ ...@@ -5,11 +5,8 @@
#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_ #ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_ #define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_
#include <map>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "base/callback_list.h" #include "base/callback_list.h"
#include "base/feature_list.h" #include "base/feature_list.h"
...@@ -20,7 +17,6 @@ ...@@ -20,7 +17,6 @@
#include "base/optional.h" #include "base/optional.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "build/buildflag.h" #include "build/buildflag.h"
#include "chrome/browser/extensions/api/identity/extension_token_key.h"
#include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h" #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
#include "chrome/browser/extensions/api/identity/identity_get_accounts_function.h" #include "chrome/browser/extensions/api/identity/identity_get_accounts_function.h"
#include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h" #include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h"
...@@ -28,6 +24,7 @@ ...@@ -28,6 +24,7 @@
#include "chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.h" #include "chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.h"
#include "chrome/browser/extensions/api/identity/identity_mint_queue.h" #include "chrome/browser/extensions/api/identity/identity_mint_queue.h"
#include "chrome/browser/extensions/api/identity/identity_remove_cached_auth_token_function.h" #include "chrome/browser/extensions/api/identity/identity_remove_cached_auth_token_function.h"
#include "chrome/browser/extensions/api/identity/identity_token_cache.h"
#include "chrome/browser/extensions/api/identity/web_auth_flow.h" #include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "components/signin/public/base/signin_buildflags.h" #include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/identity_manager.h"
...@@ -42,53 +39,9 @@ class Profile; ...@@ -42,53 +39,9 @@ class Profile;
namespace extensions { namespace extensions {
class IdentityTokenCacheValue {
public:
IdentityTokenCacheValue();
IdentityTokenCacheValue(const IdentityTokenCacheValue& other);
~IdentityTokenCacheValue();
static IdentityTokenCacheValue CreateIssueAdvice(
const IssueAdviceInfo& issue_advice);
static IdentityTokenCacheValue CreateRemoteConsent(
const RemoteConsentResolutionData& resolution_data);
static IdentityTokenCacheValue CreateRemoteConsentApproved(
const std::string& consent_result);
static IdentityTokenCacheValue CreateToken(const std::string& token,
base::TimeDelta time_to_live);
// Order of these entries is used to determine whether or not new
// entries supersede older ones in SetCachedToken.
enum CacheValueStatus {
CACHE_STATUS_NOTFOUND,
CACHE_STATUS_ADVICE,
CACHE_STATUS_REMOTE_CONSENT,
CACHE_STATUS_REMOTE_CONSENT_APPROVED,
CACHE_STATUS_TOKEN
};
CacheValueStatus status() const;
const IssueAdviceInfo& issue_advice() const;
const RemoteConsentResolutionData& resolution_data() const;
const std::string& consent_result() const;
const std::string& token() const;
const base::Time& expiration_time() const;
private:
bool is_expired() const;
CacheValueStatus status_ = CACHE_STATUS_NOTFOUND;
IssueAdviceInfo issue_advice_;
RemoteConsentResolutionData resolution_data_;
std::string consent_result_;
std::string token_;
base::Time expiration_time_;
};
class IdentityAPI : public BrowserContextKeyedAPI, class IdentityAPI : public BrowserContextKeyedAPI,
public signin::IdentityManager::Observer { public signin::IdentityManager::Observer {
public: public:
using CachedTokens = std::map<ExtensionTokenKey, IdentityTokenCacheValue>;
using OnSetConsentResultSignature = void(const std::string&, using OnSetConsentResultSignature = void(const std::string&,
const std::string&); const std::string&);
...@@ -98,14 +51,7 @@ class IdentityAPI : public BrowserContextKeyedAPI, ...@@ -98,14 +51,7 @@ class IdentityAPI : public BrowserContextKeyedAPI,
// Request serialization queue for getAuthToken. // Request serialization queue for getAuthToken.
IdentityMintRequestQueue* mint_queue(); IdentityMintRequestQueue* mint_queue();
// Token cache. IdentityTokenCache* token_cache();
void SetCachedToken(const ExtensionTokenKey& key,
const IdentityTokenCacheValue& token_data);
void EraseCachedToken(const std::string& extension_id,
const std::string& token);
void EraseAllCachedTokens();
const IdentityTokenCacheValue& GetCachedToken(const ExtensionTokenKey& key);
const CachedTokens& GetAllCachedTokens();
// GAIA id cache. // GAIA id cache.
void SetGaiaIdForExtension(const std::string& extension_id, void SetGaiaIdForExtension(const std::string& extension_id,
...@@ -180,7 +126,7 @@ class IdentityAPI : public BrowserContextKeyedAPI, ...@@ -180,7 +126,7 @@ class IdentityAPI : public BrowserContextKeyedAPI,
EventRouter* const event_router_; EventRouter* const event_router_;
IdentityMintRequestQueue mint_queue_; IdentityMintRequestQueue mint_queue_;
CachedTokens token_cache_; IdentityTokenCache token_cache_;
OnSignInChangedCallback on_signin_changed_callback_for_testing_; OnSignInChangedCallback on_signin_changed_callback_for_testing_;
......
...@@ -940,7 +940,7 @@ class GetAuthTokenFunctionTest ...@@ -940,7 +940,7 @@ class GetAuthTokenFunctionTest
void SetCachedTokenForAccount(const CoreAccountId account_id, void SetCachedTokenForAccount(const CoreAccountId account_id,
const IdentityTokenCacheValue& token_data) { const IdentityTokenCacheValue& token_data) {
ExtensionTokenKey key(extension_id_, account_id, oauth_scopes_); ExtensionTokenKey key(extension_id_, account_id, oauth_scopes_);
id_api()->SetCachedToken(key, token_data); id_api()->token_cache()->SetToken(key, token_data);
} }
void SetCachedGaiaId(const std::string& gaia_id) { void SetCachedGaiaId(const std::string& gaia_id) {
...@@ -952,7 +952,7 @@ class GetAuthTokenFunctionTest ...@@ -952,7 +952,7 @@ class GetAuthTokenFunctionTest
ExtensionTokenKey key( ExtensionTokenKey key(
extension_id_, account_id.empty() ? GetPrimaryAccountId() : account_id, extension_id_, account_id.empty() ? GetPrimaryAccountId() : account_id,
oauth_scopes_); oauth_scopes_);
return id_api()->GetCachedToken(key); return id_api()->token_cache()->GetToken(key);
} }
base::Optional<std::string> GetCachedGaiaId() { base::Optional<std::string> GetCachedGaiaId() {
...@@ -1603,7 +1603,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ...@@ -1603,7 +1603,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func->set_extension(extension.get()); func->set_extension(extension.get());
// Make sure we don't get a cached issue_advice result, which would cause // Make sure we don't get a cached issue_advice result, which would cause
// flow to be leaked. // flow to be leaked.
id_api()->EraseAllCachedTokens(); id_api()->token_cache()->EraseAllTokens();
func->push_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS); func->push_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_oauth_error(test_case.oauth_error); func->set_scope_ui_oauth_error(test_case.oauth_error);
std::string error = utils::RunFunctionAndReturnError( std::string error = utils::RunFunctionAndReturnError(
...@@ -2965,11 +2965,11 @@ class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest { ...@@ -2965,11 +2965,11 @@ class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest {
void SetCachedToken(const IdentityTokenCacheValue& token_data) { void SetCachedToken(const IdentityTokenCacheValue& token_data) {
ExtensionTokenKey key(kExtensionId, CoreAccountId("test@example.com"), ExtensionTokenKey key(kExtensionId, CoreAccountId("test@example.com"),
std::set<std::string>()); std::set<std::string>());
id_api()->SetCachedToken(key, token_data); id_api()->token_cache()->SetToken(key, token_data);
} }
const IdentityTokenCacheValue& GetCachedToken() { const IdentityTokenCacheValue& GetCachedToken() {
return id_api()->GetCachedToken( return id_api()->token_cache()->GetToken(
ExtensionTokenKey(kExtensionId, CoreAccountId("test@example.com"), ExtensionTokenKey(kExtensionId, CoreAccountId("test@example.com"),
std::set<std::string>())); std::set<std::string>()));
} }
......
...@@ -376,7 +376,7 @@ void IdentityGetAuthTokenFunction::StartSigninFlow() { ...@@ -376,7 +376,7 @@ void IdentityGetAuthTokenFunction::StartSigninFlow() {
// All cached tokens are invalid because the user is not signed in. // All cached tokens are invalid because the user is not signed in.
IdentityAPI* id_api = IdentityAPI* id_api =
extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile());
id_api->EraseAllCachedTokens(); id_api->token_cache()->EraseAllTokens();
// If the signin flow fails, don't display the login prompt again. // If the signin flow fails, don't display the login prompt again.
should_prompt_for_signin_ = false; should_prompt_for_signin_ = false;
...@@ -475,7 +475,8 @@ void IdentityGetAuthTokenFunction::StartMintToken( ...@@ -475,7 +475,8 @@ void IdentityGetAuthTokenFunction::StartMintToken(
const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension()); const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile()); IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile());
IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(token_key_); IdentityTokenCacheValue cache_entry =
id_api->token_cache()->GetToken(token_key_);
IdentityTokenCacheValue::CacheValueStatus cache_status = cache_entry.status(); IdentityTokenCacheValue::CacheValueStatus cache_status = cache_entry.status();
if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) { if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
...@@ -577,7 +578,8 @@ void IdentityGetAuthTokenFunction::OnMintTokenSuccess( ...@@ -577,7 +578,8 @@ void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
access_token, base::TimeDelta::FromSeconds(time_to_live)); access_token, base::TimeDelta::FromSeconds(time_to_live));
IdentityAPI::GetFactoryInstance() IdentityAPI::GetFactoryInstance()
->Get(GetProfile()) ->Get(GetProfile())
->SetCachedToken(token_key_, token); ->token_cache()
->SetToken(token_key_, token);
CompleteMintTokenFlow(); CompleteMintTokenFlow();
CompleteFunctionWithResult(access_token, granted_scopes); CompleteFunctionWithResult(access_token, granted_scopes);
...@@ -617,7 +619,7 @@ void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess( ...@@ -617,7 +619,7 @@ void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
IdentityAPI* identity_api = IdentityAPI* identity_api =
IdentityAPI::GetFactoryInstance()->Get(GetProfile()); IdentityAPI::GetFactoryInstance()->Get(GetProfile());
identity_api->SetCachedToken( identity_api->token_cache()->SetToken(
token_key_, IdentityTokenCacheValue::CreateIssueAdvice(issue_advice)); token_key_, IdentityTokenCacheValue::CreateIssueAdvice(issue_advice));
// IssueAdvice doesn't communicate back to Chrome which account has been // IssueAdvice doesn't communicate back to Chrome which account has been
// chosen by the user. Cached gaia id may contain incorrect information so // chosen by the user. Cached gaia id may contain incorrect information so
...@@ -639,8 +641,9 @@ void IdentityGetAuthTokenFunction::OnRemoteConsentSuccess( ...@@ -639,8 +641,9 @@ void IdentityGetAuthTokenFunction::OnRemoteConsentSuccess(
IdentityAPI::GetFactoryInstance() IdentityAPI::GetFactoryInstance()
->Get(GetProfile()) ->Get(GetProfile())
->SetCachedToken(token_key_, IdentityTokenCacheValue::CreateRemoteConsent( ->token_cache()
resolution_data)); ->SetToken(token_key_,
IdentityTokenCacheValue::CreateRemoteConsent(resolution_data));
should_prompt_for_signin_ = false; should_prompt_for_signin_ = false;
resolution_data_ = resolution_data; resolution_data_ = resolution_data;
CompleteMintTokenFlow(); CompleteMintTokenFlow();
...@@ -759,7 +762,8 @@ void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted( ...@@ -759,7 +762,8 @@ void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
access_token, base::TimeDelta::FromSeconds(time_to_live)); access_token, base::TimeDelta::FromSeconds(time_to_live));
IdentityAPI::GetFactoryInstance() IdentityAPI::GetFactoryInstance()
->Get(GetProfile()) ->Get(GetProfile())
->SetCachedToken(token_key_, token_value); ->token_cache()
->SetToken(token_key_, token_value);
} }
CompleteMintTokenFlow(); CompleteMintTokenFlow();
...@@ -842,7 +846,7 @@ void IdentityGetAuthTokenFunction::OnGaiaRemoteConsentFlowApproved( ...@@ -842,7 +846,7 @@ void IdentityGetAuthTokenFunction::OnGaiaRemoteConsentFlowApproved(
// as this call may start a new request synchronously and query the cache. // as this call may start a new request synchronously and query the cache.
ExtensionTokenKey new_token_key(token_key_); ExtensionTokenKey new_token_key(token_key_);
new_token_key.account_id = account->account_id; new_token_key.account_id = account->account_id;
id_api->SetCachedToken( id_api->token_cache()->SetToken(
new_token_key, new_token_key,
IdentityTokenCacheValue::CreateRemoteConsentApproved(consent_result)); IdentityTokenCacheValue::CreateRemoteConsentApproved(consent_result));
CompleteMintTokenFlow(); CompleteMintTokenFlow();
......
...@@ -26,7 +26,8 @@ ExtensionFunction::ResponseAction IdentityRemoveCachedAuthTokenFunction::Run() { ...@@ -26,7 +26,8 @@ ExtensionFunction::ResponseAction IdentityRemoveCachedAuthTokenFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
IdentityAPI::GetFactoryInstance() IdentityAPI::GetFactoryInstance()
->Get(browser_context()) ->Get(browser_context())
->EraseCachedToken(extension()->id(), params->details.token); ->token_cache()
->EraseToken(extension()->id(), params->details.token);
return RespondNow(NoArguments()); return RespondNow(NoArguments());
} }
......
// Copyright 2020 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 "chrome/browser/extensions/api/identity/identity_token_cache.h"
#include <algorithm>
#include "base/stl_util.h"
#include "chrome/browser/extensions/api/identity/identity_constants.h"
namespace extensions {
IdentityTokenCacheValue::IdentityTokenCacheValue() = default;
IdentityTokenCacheValue::IdentityTokenCacheValue(
const IdentityTokenCacheValue& other) = default;
IdentityTokenCacheValue& IdentityTokenCacheValue::operator=(
const IdentityTokenCacheValue& other) = default;
IdentityTokenCacheValue::~IdentityTokenCacheValue() = default;
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateIssueAdvice(
const IssueAdviceInfo& issue_advice) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_ADVICE;
cache_value.issue_advice_ = issue_advice;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsent(
const RemoteConsentResolutionData& resolution_data) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT;
cache_value.resolution_data_ = resolution_data;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateRemoteConsentApproved(
const std::string& consent_result) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_REMOTE_CONSENT_APPROVED;
cache_value.consent_result_ = consent_result;
cache_value.expiration_time_ =
base::Time::Now() + base::TimeDelta::FromSeconds(
identity_constants::kCachedIssueAdviceTTLSeconds);
return cache_value;
}
// static
IdentityTokenCacheValue IdentityTokenCacheValue::CreateToken(
const std::string& token,
base::TimeDelta time_to_live) {
IdentityTokenCacheValue cache_value;
cache_value.status_ = CACHE_STATUS_TOKEN;
cache_value.token_ = token;
// Remove 20 minutes from the ttl so cached tokens will have some time
// to live any time they are returned.
time_to_live -= base::TimeDelta::FromMinutes(20);
base::TimeDelta zero_delta;
if (time_to_live < zero_delta)
time_to_live = zero_delta;
cache_value.expiration_time_ = base::Time::Now() + time_to_live;
return cache_value;
}
IdentityTokenCacheValue::CacheValueStatus IdentityTokenCacheValue::status()
const {
if (is_expired())
return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
else
return status_;
}
bool IdentityTokenCacheValue::is_expired() const {
return status_ == CACHE_STATUS_NOTFOUND ||
expiration_time_ < base::Time::Now();
}
const base::Time& IdentityTokenCacheValue::expiration_time() const {
return expiration_time_;
}
const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
return issue_advice_;
}
const RemoteConsentResolutionData& IdentityTokenCacheValue::resolution_data()
const {
return resolution_data_;
}
const std::string& IdentityTokenCacheValue::consent_result() const {
return consent_result_;
}
const std::string& IdentityTokenCacheValue::token() const {
return token_;
}
IdentityTokenCache::IdentityTokenCache() = default;
IdentityTokenCache::~IdentityTokenCache() = default;
void IdentityTokenCache::SetToken(const ExtensionTokenKey& key,
const IdentityTokenCacheValue& token_data) {
auto it = token_cache_.find(key);
if (it != token_cache_.end() && it->second.status() <= token_data.status())
token_cache_.erase(it);
token_cache_.insert(std::make_pair(key, token_data));
}
void IdentityTokenCache::EraseToken(const std::string& extension_id,
const std::string& token) {
CachedTokens::iterator it;
for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
if (it->first.extension_id == extension_id &&
it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
it->second.token() == token) {
token_cache_.erase(it);
break;
}
}
}
void IdentityTokenCache::EraseAllTokens() {
token_cache_.clear();
}
const IdentityTokenCacheValue& IdentityTokenCache::GetToken(
const ExtensionTokenKey& key) {
return token_cache_[key];
}
const IdentityTokenCache::CachedTokens& IdentityTokenCache::GetAllTokens() {
return token_cache_;
}
} // namespace extensions
// Copyright 2020 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.
#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_TOKEN_CACHE_H_
#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_TOKEN_CACHE_H_
#include <map>
#include <set>
#include <string>
#include "base/time/time.h"
#include "chrome/browser/extensions/api/identity/extension_token_key.h"
#include "google_apis/gaia/oauth2_mint_token_flow.h"
namespace extensions {
class IdentityTokenCacheValue {
public:
IdentityTokenCacheValue();
IdentityTokenCacheValue(const IdentityTokenCacheValue& other);
IdentityTokenCacheValue& operator=(const IdentityTokenCacheValue& other);
~IdentityTokenCacheValue();
static IdentityTokenCacheValue CreateIssueAdvice(
const IssueAdviceInfo& issue_advice);
static IdentityTokenCacheValue CreateRemoteConsent(
const RemoteConsentResolutionData& resolution_data);
static IdentityTokenCacheValue CreateRemoteConsentApproved(
const std::string& consent_result);
static IdentityTokenCacheValue CreateToken(const std::string& token,
base::TimeDelta time_to_live);
// Order of these entries is used to determine whether or not new
// entries supersede older ones in SetCachedToken.
enum CacheValueStatus {
CACHE_STATUS_NOTFOUND,
CACHE_STATUS_ADVICE,
CACHE_STATUS_REMOTE_CONSENT,
CACHE_STATUS_REMOTE_CONSENT_APPROVED,
CACHE_STATUS_TOKEN
};
CacheValueStatus status() const;
const base::Time& expiration_time() const;
const IssueAdviceInfo& issue_advice() const;
const RemoteConsentResolutionData& resolution_data() const;
const std::string& consent_result() const;
const std::string& token() const;
private:
bool is_expired() const;
CacheValueStatus status_ = CACHE_STATUS_NOTFOUND;
base::Time expiration_time_;
// TODO(alexilin): This class holds at any given time one of the several
// possible types. Consider rewriting using absl::variant
IssueAdviceInfo issue_advice_;
RemoteConsentResolutionData resolution_data_;
std::string consent_result_;
std::string token_;
};
// In-memory cache of OAuth2 access tokens that are requested by extensions
// through the `getAuthToken` API. Also caches intermediate short-lived values
// used at different stages of the `getAuthToken` flow before a token is
// obtained. The cache automatically handles token expiration. Extensions can
// manually remove tokens from the cache using `removeCachedAuthToken` API.
//
// chrome://identity-internals provides a view of cache's content for debugging.
class IdentityTokenCache {
public:
IdentityTokenCache();
~IdentityTokenCache();
IdentityTokenCache(const IdentityTokenCache& other) = delete;
IdentityTokenCache& operator=(const IdentityTokenCache& other) = delete;
using CachedTokens = std::map<ExtensionTokenKey, IdentityTokenCacheValue>;
void SetToken(const ExtensionTokenKey& key,
const IdentityTokenCacheValue& token_data);
void EraseToken(const std::string& extension_id, const std::string& token);
void EraseAllTokens();
const IdentityTokenCacheValue& GetToken(const ExtensionTokenKey& key);
const CachedTokens& GetAllTokens();
private:
CachedTokens token_cache_;
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_TOKEN_CACHE_H_
// Copyright 2020 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 "chrome/browser/extensions/api/identity/identity_token_cache.h"
#include <set>
#include <string>
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
const char kDefaultExtensionId[] = "ext_id";
} // namespace
class IdentityTokenCacheTest : public testing::Test {
public:
void SetAccessToken(const std::string& ext_id,
const std::string& token_string,
const std::set<std::string>& scopes) {
ExtensionTokenKey key(ext_id, CoreAccountId(), scopes);
IdentityTokenCacheValue token = IdentityTokenCacheValue::CreateToken(
token_string, base::TimeDelta::FromSeconds(3600));
cache_.SetToken(key, token);
}
void SetRemoteConsentApprovedToken(const std::string& ext_id,
const std::string& consent_result,
const std::set<std::string>& scopes) {
ExtensionTokenKey key(ext_id, CoreAccountId(), scopes);
IdentityTokenCacheValue token =
IdentityTokenCacheValue::CreateRemoteConsentApproved(consent_result);
cache_.SetToken(key, token);
}
const IdentityTokenCacheValue& GetToken(const std::string& ext_id,
const std::set<std::string>& scopes) {
ExtensionTokenKey key(ext_id, CoreAccountId(), scopes);
return cache_.GetToken(key);
}
IdentityTokenCache& cache() { return cache_; }
private:
IdentityTokenCache cache_;
};
TEST_F(IdentityTokenCacheTest, AccessTokenCacheHit) {
std::string token_string = "token";
std::set<std::string> scopes = {"foo", "bar"};
SetAccessToken(kDefaultExtensionId, token_string, scopes);
const IdentityTokenCacheValue& cached_token =
GetToken(kDefaultExtensionId, scopes);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status());
EXPECT_EQ(token_string, cached_token.token());
}
TEST_F(IdentityTokenCacheTest, IntermediateValueCacheHit) {
std::string consent_result = "result";
std::set<std::string> scopes = {"foo", "bar"};
SetRemoteConsentApprovedToken(kDefaultExtensionId, consent_result, scopes);
const IdentityTokenCacheValue& cached_token =
GetToken(kDefaultExtensionId, scopes);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT_APPROVED,
cached_token.status());
EXPECT_EQ(consent_result, cached_token.consent_result());
}
TEST_F(IdentityTokenCacheTest, CacheHitPriority) {
std::string token_string = "token";
std::set<std::string> scopes = {"foo", "bar"};
SetAccessToken(kDefaultExtensionId, token_string, scopes);
SetRemoteConsentApprovedToken(kDefaultExtensionId, "result", scopes);
// Prioritize access tokens over immediate values.
const IdentityTokenCacheValue& cached_token =
GetToken(kDefaultExtensionId, scopes);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status());
EXPECT_EQ(token_string, cached_token.token());
}
TEST_F(IdentityTokenCacheTest, AccessTokenCacheMiss) {
std::string ext_1 = "ext_1";
std::set<std::string> scopes_1 = {"foo", "bar"};
SetAccessToken(ext_1, "token_1", scopes_1);
std::string ext_2 = "ext_2";
std::set<std::string> scopes_2 = {"foo", "foobar"};
SetAccessToken(ext_2, "token_2", scopes_2);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(ext_2, scopes_1).status());
}
TEST_F(IdentityTokenCacheTest, IntermediateValueCacheMiss) {
std::string ext_1 = "ext_1";
std::set<std::string> scopes_1 = {"foo", "bar"};
SetRemoteConsentApprovedToken(ext_1, "result_1", scopes_1);
std::string ext_2 = "ext_2";
std::set<std::string> scopes_2 = {"foo", "foobar"};
SetRemoteConsentApprovedToken(ext_2, "result_2", scopes_2);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(ext_2, scopes_1).status());
}
TEST_F(IdentityTokenCacheTest, EraseToken) {
std::string token_string = "token";
std::set<std::string> scopes = {"foo", "bar"};
SetAccessToken(kDefaultExtensionId, token_string, scopes);
cache().EraseToken(kDefaultExtensionId, token_string);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(kDefaultExtensionId, scopes).status());
}
TEST_F(IdentityTokenCacheTest, EraseTokenOthersUnaffected) {
std::string token_string = "token";
std::set<std::string> scopes = {"foo", "bar"};
SetAccessToken(kDefaultExtensionId, token_string, scopes);
std::string unrelated_token_string = "unrelated_token";
std::set<std::string> unrelated_scopes = {"foo", "foobar"};
SetAccessToken(kDefaultExtensionId, unrelated_token_string, unrelated_scopes);
cache().EraseToken(kDefaultExtensionId, token_string);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(kDefaultExtensionId, scopes).status());
const IdentityTokenCacheValue& cached_token =
GetToken(kDefaultExtensionId, unrelated_scopes);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status());
EXPECT_EQ(unrelated_token_string, cached_token.token());
}
TEST_F(IdentityTokenCacheTest, EraseAllTokens) {
std::string ext_1 = "ext_1";
std::string token_string = "token_1";
std::set<std::string> scopes_1 = {"foo", "bar"};
SetAccessToken(ext_1, token_string, scopes_1);
std::string ext_2 = "ext_2";
std::string remote_consent = "approved";
std::set<std::string> scopes_2 = {"foo", "foobar"};
SetRemoteConsentApprovedToken(ext_2, remote_consent, scopes_2);
cache().EraseAllTokens();
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(ext_1, scopes_1).status());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(ext_2, scopes_2).status());
}
TEST_F(IdentityTokenCacheTest, GetAllTokens) {
std::string ext_1 = "ext_1";
std::string token_string_1 = "token_1";
std::set<std::string> scopes_1 = {"foo", "bar"};
SetAccessToken(ext_1, token_string_1, scopes_1);
std::string ext_2 = "ext_2";
std::string token_string_2 = "token_2";
std::set<std::string> scopes_2 = {"foobar"};
SetAccessToken(ext_2, token_string_2, scopes_2);
IdentityTokenCache::CachedTokens cached_tokens = cache().GetAllTokens();
EXPECT_EQ(2ul, cached_tokens.size());
ExtensionTokenKey key_1(ext_1, CoreAccountId(), scopes_1);
const IdentityTokenCacheValue& cached_token_1 = cached_tokens[key_1];
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
cached_token_1.status());
EXPECT_EQ(token_string_1, cached_token_1.token());
ExtensionTokenKey key_2(ext_2, CoreAccountId(), scopes_2);
const IdentityTokenCacheValue& cached_token_2 = cached_tokens[key_2];
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
cached_token_2.status());
EXPECT_EQ(token_string_2, cached_token_2.token());
}
// Newly cached access tokens should override previously cached values with the
// same scopes.
TEST_F(IdentityTokenCacheTest, OverrideAccessToken) {
std::set<std::string> scopes = {"foo", "bar", "foobar"};
SetAccessToken(kDefaultExtensionId, "token1", scopes);
std::string override_token = "token_2";
SetAccessToken(kDefaultExtensionId, override_token, scopes);
cache().EraseToken(kDefaultExtensionId, override_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(kDefaultExtensionId, scopes).status());
}
TEST_F(IdentityTokenCacheTest, OverrideIntermediateToken) {
std::set<std::string> scopes = {"foo", "bar", "foobar"};
SetRemoteConsentApprovedToken(kDefaultExtensionId, "result", scopes);
std::string override_token = "token";
SetAccessToken(kDefaultExtensionId, override_token, scopes);
cache().EraseToken(kDefaultExtensionId, override_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetToken(kDefaultExtensionId, scopes).status());
}
} // namespace extensions
...@@ -140,8 +140,8 @@ void IdentityInternalsUIMessageHandler::OnTokenRevokerDone( ...@@ -140,8 +140,8 @@ void IdentityInternalsUIMessageHandler::OnTokenRevokerDone(
CHECK(api); CHECK(api);
// Remove token from the cache. // Remove token from the cache.
api->EraseCachedToken(token_revoker->extension_id(), api->token_cache()->EraseToken(token_revoker->extension_id(),
token_revoker->access_token()); token_revoker->access_token());
// Update view about the token being removed. // Update view about the token being removed.
base::ListValue result; base::ListValue result;
...@@ -223,16 +223,15 @@ IdentityInternalsUIMessageHandler::GetInfoForToken( ...@@ -223,16 +223,15 @@ IdentityInternalsUIMessageHandler::GetInfoForToken(
void IdentityInternalsUIMessageHandler::GetInfoForAllTokens( void IdentityInternalsUIMessageHandler::GetInfoForAllTokens(
const base::ListValue* args) { const base::ListValue* args) {
base::ListValue results; base::ListValue results;
extensions::IdentityAPI::CachedTokens tokens; extensions::IdentityTokenCache::CachedTokens tokens;
// The API can be null in incognito. // The API can be null in incognito.
extensions::IdentityAPI* api = extensions::IdentityAPI* api =
extensions::IdentityAPI::GetFactoryInstance()->Get( extensions::IdentityAPI::GetFactoryInstance()->Get(
Profile::FromWebUI(web_ui())); Profile::FromWebUI(web_ui()));
if (api) if (api)
tokens = api->GetAllCachedTokens(); tokens = api->token_cache()->GetAllTokens();
for (extensions::IdentityAPI::CachedTokens::const_iterator for (const auto& token : tokens) {
iter = tokens.begin(); iter != tokens.end(); ++iter) { results.Append(GetInfoForToken(token.first, token.second));
results.Append(GetInfoForToken(iter->first, iter->second));
} }
web_ui()->CallJavascriptFunctionUnsafe("identity_internals.returnTokens", web_ui()->CallJavascriptFunctionUnsafe("identity_internals.returnTokens",
......
...@@ -59,5 +59,6 @@ void IdentityInternalsUIBrowserTest::AddTokenToCache( ...@@ -59,5 +59,6 @@ void IdentityInternalsUIBrowserTest::AddTokenToCache(
std::set<std::string>(scopes.begin(), scopes.end())); std::set<std::string>(scopes.begin(), scopes.end()));
extensions::IdentityAPI::GetFactoryInstance() extensions::IdentityAPI::GetFactoryInstance()
->Get(browser()->profile()) ->Get(browser()->profile())
->SetCachedToken(key, token_cache_value); ->token_cache()
->SetToken(key, token_cache_value);
} }
...@@ -4794,6 +4794,7 @@ test("unit_tests") { ...@@ -4794,6 +4794,7 @@ test("unit_tests") {
"../browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc", "../browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc",
"../browser/extensions/api/identity/identity_api_unittest.cc", "../browser/extensions/api/identity/identity_api_unittest.cc",
"../browser/extensions/api/identity/identity_mint_queue_unittest.cc", "../browser/extensions/api/identity/identity_mint_queue_unittest.cc",
"../browser/extensions/api/identity/identity_token_cache_unittest.cc",
"../browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc", "../browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc",
"../browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc", "../browser/extensions/api/image_writer_private/image_writer_private_api_unittest.cc",
"../browser/extensions/api/image_writer_private/operation_manager_unittest.cc", "../browser/extensions/api/image_writer_private/operation_manager_unittest.cc",
......
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