| // 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 "base/test/task_environment.h" |
| #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) { |
| SetAccessTokenInternal(ext_id, token_string, scopes, base::Seconds(3600)); |
| } |
| |
| void SetExpiredAccessToken(const std::string& ext_id, |
| const std::string& token_string, |
| const std::set<std::string>& scopes) { |
| // Token must not be expired at the insertion moment. |
| SetAccessTokenInternal(ext_id, token_string, scopes, base::Milliseconds(1)); |
| task_environment_.FastForwardBy(base::Milliseconds(2)); |
| } |
| |
| void SetRemoteConsentApprovedToken(const std::string& ext_id, |
| const std::string& consent_result, |
| const std::set<std::string>& scopes) { |
| ExtensionTokenKey key(ext_id, CoreAccountInfo(), 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, CoreAccountInfo(), scopes); |
| return cache_.GetToken(key); |
| } |
| |
| IdentityTokenCache& cache() { return cache_; } |
| |
| private: |
| void SetAccessTokenInternal(const std::string& ext_id, |
| const std::string& token_string, |
| const std::set<std::string>& scopes, |
| base::TimeDelta time_to_live) { |
| ExtensionTokenKey key(ext_id, CoreAccountInfo(), scopes); |
| IdentityTokenCacheValue token = IdentityTokenCacheValue::CreateToken( |
| token_string, scopes, time_to_live); |
| cache_.SetToken(key, token); |
| } |
| |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| 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()); |
| EXPECT_EQ(scopes, cached_token.granted_scopes()); |
| } |
| |
| // The cache should return NOTFOUND status when a token expires. |
| // Regression test for https://crbug.com/1127187. |
| TEST_F(IdentityTokenCacheTest, ExpiredAccessTokenCacheHit) { |
| std::string token_string = "token"; |
| std::set<std::string> scopes = {"foo", "bar"}; |
| SetExpiredAccessToken(kDefaultExtensionId, token_string, scopes); |
| |
| const IdentityTokenCacheValue& cached_token = |
| GetToken(kDefaultExtensionId, scopes); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| cached_token.status()); |
| } |
| |
| 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()); |
| EXPECT_EQ(scopes, cached_token.granted_scopes()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, CacheHitAfterExpired) { |
| std::string token_string = "token"; |
| std::set<std::string> scopes = {"foo", "bar"}; |
| SetExpiredAccessToken(kDefaultExtensionId, token_string, scopes); |
| |
| std::string consent_result = "result"; |
| 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, 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, EraseAccessToken) { |
| std::string token_string = "token"; |
| std::set<std::string> scopes = {"foo", "bar"}; |
| SetAccessToken(kDefaultExtensionId, token_string, scopes); |
| |
| cache().EraseAccessToken(kDefaultExtensionId, token_string); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetToken(kDefaultExtensionId, scopes).status()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, EraseAccessTokenOthersUnaffected) { |
| 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().EraseAccessToken(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()); |
| EXPECT_EQ(unrelated_scopes, cached_token.granted_scopes()); |
| } |
| |
| 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, EraseAllTokensForExtension) { |
| std::string token_string = "token"; |
| std::set<std::string> scopes_1 = {"foo", "bar"}; |
| SetAccessToken(kDefaultExtensionId, token_string, scopes_1); |
| |
| std::string remote_consent = "approved"; |
| std::set<std::string> scopes_2 = {"foo", "foobar"}; |
| SetRemoteConsentApprovedToken(kDefaultExtensionId, remote_consent, scopes_2); |
| |
| std::string unrelated_extension = "ext_unrelated"; |
| SetAccessToken(unrelated_extension, token_string, scopes_1); |
| SetRemoteConsentApprovedToken(unrelated_extension, remote_consent, scopes_2); |
| |
| cache().EraseAllTokensForExtension(kDefaultExtensionId); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetToken(kDefaultExtensionId, scopes_1).status()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetToken(kDefaultExtensionId, scopes_2).status()); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetToken(unrelated_extension, scopes_1).status()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT_APPROVED, |
| GetToken(unrelated_extension, scopes_2).status()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, GetAccessTokens) { |
| 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::AccessTokensCache cached_access_tokens = |
| cache().access_tokens_cache(); |
| EXPECT_EQ(2ul, cached_access_tokens.size()); |
| |
| IdentityTokenCache::AccessTokensKey key_1(ext_1, CoreAccountId()); |
| const IdentityTokenCache::AccessTokensValue& cached_tokens_1 = |
| cached_access_tokens[key_1]; |
| EXPECT_EQ(1ul, cached_tokens_1.size()); |
| |
| const IdentityTokenCacheValue& cached_token_1 = *(cached_tokens_1.begin()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| cached_token_1.status()); |
| EXPECT_EQ(token_string_1, cached_token_1.token()); |
| EXPECT_EQ(scopes_1, cached_token_1.granted_scopes()); |
| |
| IdentityTokenCache::AccessTokensKey key_2(ext_2, CoreAccountId()); |
| const IdentityTokenCache::AccessTokensValue& cached_tokens_2 = |
| cached_access_tokens[key_2]; |
| EXPECT_EQ(1ul, cached_tokens_2.size()); |
| |
| const IdentityTokenCacheValue& cached_token_2 = *(cached_tokens_2.begin()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| cached_token_2.status()); |
| EXPECT_EQ(token_string_2, cached_token_2.token()); |
| EXPECT_EQ(scopes_2, cached_token_2.granted_scopes()); |
| } |
| |
| // 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().EraseAccessToken(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().EraseAccessToken(kDefaultExtensionId, override_token); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetToken(kDefaultExtensionId, scopes).status()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, SubsetMatchCacheHit) { |
| std::set<std::string> superset_scopes = {"foo", "bar"}; |
| std::string token_string = "token"; |
| SetAccessToken(kDefaultExtensionId, token_string, superset_scopes); |
| |
| const IdentityTokenCacheValue& cached_token = |
| GetToken(kDefaultExtensionId, std::set<std::string>({"foo"})); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status()); |
| EXPECT_EQ(token_string, cached_token.token()); |
| EXPECT_EQ(superset_scopes, cached_token.granted_scopes()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, SubsetMatchCacheHitPriority) { |
| std::set<std::string> scopes_smallest = {"foo"}; |
| SetRemoteConsentApprovedToken(kDefaultExtensionId, "result", scopes_smallest); |
| |
| std::set<std::string> scopes_large = {"foo", "bar", "foobar"}; |
| std::string token_string_large = "token_large"; |
| SetAccessToken(kDefaultExtensionId, token_string_large, scopes_large); |
| |
| std::set<std::string> scopes_small = {"foo", "bar"}; |
| std::string token_string_small = "token_small"; |
| SetAccessToken(kDefaultExtensionId, token_string_small, scopes_small); |
| |
| const IdentityTokenCacheValue& cached_token = |
| GetToken(kDefaultExtensionId, std::set<std::string>({"foo"})); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status()); |
| EXPECT_EQ(token_string_small, cached_token.token()); |
| EXPECT_EQ(scopes_small, cached_token.granted_scopes()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, SubsetMatchCacheHitPriorityOneExpired) { |
| std::set<std::string> scopes_smallest = {"foo"}; |
| std::string consent_result = "result"; |
| SetRemoteConsentApprovedToken(kDefaultExtensionId, consent_result, |
| scopes_smallest); |
| |
| std::set<std::string> scopes_small = {"foo", "bar"}; |
| std::string token_string_small = "token_small"; |
| SetExpiredAccessToken(kDefaultExtensionId, token_string_small, scopes_small); |
| |
| std::set<std::string> scopes_large = {"foo", "bar", "foobar"}; |
| std::string token_string_large = "token_large"; |
| SetAccessToken(kDefaultExtensionId, token_string_large, scopes_large); |
| |
| const IdentityTokenCacheValue& cached_token = |
| GetToken(kDefaultExtensionId, scopes_small); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, cached_token.status()); |
| EXPECT_EQ(token_string_large, cached_token.token()); |
| EXPECT_EQ(scopes_large, cached_token.granted_scopes()); |
| } |
| |
| TEST_F(IdentityTokenCacheTest, SubsetMatchCacheHitPriorityTwoExpired) { |
| std::set<std::string> scopes_smallest = {"foo"}; |
| std::string consent_result = "result"; |
| SetRemoteConsentApprovedToken(kDefaultExtensionId, consent_result, |
| scopes_smallest); |
| |
| std::set<std::string> scopes_small = {"foo", "bar"}; |
| std::string token_string_small = "token_small"; |
| SetExpiredAccessToken(kDefaultExtensionId, token_string_small, scopes_small); |
| |
| std::set<std::string> scopes_large = {"foo", "bar", "foobar"}; |
| std::string token_string_large = "token_large"; |
| SetExpiredAccessToken(kDefaultExtensionId, token_string_large, scopes_large); |
| |
| const IdentityTokenCacheValue& cached_token = |
| GetToken(kDefaultExtensionId, std::set<std::string>({"foo"})); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT_APPROVED, |
| cached_token.status()); |
| EXPECT_EQ(consent_result, cached_token.consent_result()); |
| } |
| |
| } // namespace extensions |