| // Copyright (c) 2023 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 "quiche/blind_sign_auth/cached_blind_sign_auth.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "quiche/blind_sign_auth/blind_sign_auth_interface.h" |
| #include "quiche/blind_sign_auth/test_tools/mock_blind_sign_auth_interface.h" |
| #include "quiche/common/platform/api/quiche_mutex.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| |
| namespace quiche { |
| namespace test { |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::InvokeArgument; |
| using ::testing::Unused; |
| |
| class CachedBlindSignAuthTest : public QuicheTest { |
| protected: |
| void SetUp() override { |
| cached_blind_sign_auth_ = |
| std::make_unique<CachedBlindSignAuth>(&mock_blind_sign_auth_interface_); |
| } |
| |
| void TearDown() override { |
| fake_tokens_.clear(); |
| cached_blind_sign_auth_.reset(); |
| } |
| |
| public: |
| std::vector<BlindSignToken> MakeFakeTokens(int num_tokens) { |
| std::vector<BlindSignToken> fake_tokens; |
| for (int i = 0; i < kBlindSignAuthRequestMaxTokens; i++) { |
| fake_tokens.push_back(BlindSignToken{absl::StrCat("token:", i), |
| absl::Now() + absl::Hours(1)}); |
| } |
| return fake_tokens; |
| } |
| |
| std::vector<BlindSignToken> MakeExpiredTokens(int num_tokens) { |
| std::vector<BlindSignToken> fake_tokens; |
| for (int i = 0; i < kBlindSignAuthRequestMaxTokens; i++) { |
| fake_tokens.push_back(BlindSignToken{absl::StrCat("token:", i), |
| absl::Now() - absl::Hours(1)}); |
| } |
| return fake_tokens; |
| } |
| |
| MockBlindSignAuthInterface mock_blind_sign_auth_interface_; |
| std::unique_ptr<CachedBlindSignAuth> cached_blind_sign_auth_; |
| std::string oauth_token_ = "oauth_token"; |
| std::vector<BlindSignToken> fake_tokens_; |
| }; |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensOneCallSuccessful) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(1) |
| .WillOnce( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeFakeTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = 5; |
| QuicheNotification done; |
| SignedTokenCallback callback = |
| [num_tokens, &done](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i)); |
| } |
| done.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, std::move(callback)); |
| done.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensMultipleRemoteCallsSuccessful) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(2) |
| .WillRepeatedly( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeFakeTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens - 1; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [num_tokens, &first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i)); |
| } |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| |
| QuicheNotification second; |
| SignedTokenCallback second_callback = |
| [num_tokens, &second](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| EXPECT_EQ(tokens->at(0).token, |
| absl::StrCat("token:", kBlindSignAuthRequestMaxTokens - 1)); |
| for (int i = 1; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i - 1)); |
| } |
| second.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, |
| std::move(second_callback)); |
| second.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensSecondRequestFilledFromCache) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(1) |
| .WillOnce( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeFakeTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens / 2; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [num_tokens, &first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i)); |
| } |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| |
| QuicheNotification second; |
| SignedTokenCallback second_callback = |
| [num_tokens, &second](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, |
| absl::StrCat("token:", i + num_tokens)); |
| } |
| second.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, |
| std::move(second_callback)); |
| second.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensThirdRequestRefillsCache) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(2) |
| .WillRepeatedly( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeFakeTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens / 2; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [num_tokens, &first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i)); |
| } |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| |
| QuicheNotification second; |
| SignedTokenCallback second_callback = |
| [num_tokens, &second](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(num_tokens, tokens->size()); |
| for (int i = 0; i < num_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, |
| absl::StrCat("token:", i + num_tokens)); |
| } |
| second.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, |
| std::move(second_callback)); |
| second.WaitForNotification(); |
| |
| QuicheNotification third; |
| int third_request_tokens = 10; |
| SignedTokenCallback third_callback = |
| [third_request_tokens, |
| &third](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(third_request_tokens, tokens->size()); |
| for (int i = 0; i < third_request_tokens; i++) { |
| EXPECT_EQ(tokens->at(i).token, absl::StrCat("token:", i)); |
| } |
| third.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, third_request_tokens, |
| ProxyLayer::kProxyA, |
| std::move(third_callback)); |
| third.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensRequestTooLarge) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(0); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens + 1; |
| SignedTokenCallback callback = |
| [](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), absl::StatusCode::kInvalidArgument); |
| EXPECT_THAT( |
| tokens.status().message(), |
| absl::StrFormat("Number of tokens requested exceeds maximum: %d", |
| kBlindSignAuthRequestMaxTokens)); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, std::move(callback)); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensRequestNegative) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(0); |
| |
| int num_tokens = -1; |
| SignedTokenCallback callback = |
| [num_tokens](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), absl::StatusCode::kInvalidArgument); |
| EXPECT_THAT(tokens.status().message(), |
| absl::StrFormat("Negative number of tokens requested: %d", |
| num_tokens)); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, std::move(callback)); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestHandleGetTokensResponseErrorHandling) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(2) |
| .WillOnce( |
| [](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| std::move(callback)(absl::InternalError("AuthAndSign failed")); |
| }) |
| .WillOnce( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeFakeTokens(num_tokens); |
| fake_tokens_.pop_back(); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [&first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), absl::StatusCode::kInternal); |
| EXPECT_THAT(tokens.status().message(), "AuthAndSign failed"); |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| |
| QuicheNotification second; |
| SignedTokenCallback second_callback = |
| [&second](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), |
| absl::StatusCode::kResourceExhausted); |
| second.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, |
| std::move(second_callback)); |
| second.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestGetTokensZeroTokensRequested) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(0); |
| |
| int num_tokens = 0; |
| SignedTokenCallback callback = |
| [](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| QUICHE_EXPECT_OK(tokens); |
| EXPECT_EQ(tokens->size(), 0); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, std::move(callback)); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestExpiredTokensArePruned) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(1) |
| .WillOnce( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeExpiredTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [&first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), |
| absl::StatusCode::kResourceExhausted); |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| } |
| |
| TEST_F(CachedBlindSignAuthTest, TestClearCacheRemovesTokens) { |
| EXPECT_CALL(mock_blind_sign_auth_interface_, |
| GetTokens(oauth_token_, kBlindSignAuthRequestMaxTokens, _, _)) |
| .Times(2) |
| .WillRepeatedly( |
| [this](Unused, int num_tokens, Unused, SignedTokenCallback callback) { |
| fake_tokens_ = MakeExpiredTokens(num_tokens); |
| std::move(callback)(absl::MakeSpan(fake_tokens_)); |
| }); |
| |
| int num_tokens = kBlindSignAuthRequestMaxTokens / 2; |
| QuicheNotification first; |
| SignedTokenCallback first_callback = |
| [&first](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), |
| absl::StatusCode::kResourceExhausted); |
| first.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens( |
| oauth_token_, num_tokens, ProxyLayer::kProxyA, std::move(first_callback)); |
| first.WaitForNotification(); |
| |
| cached_blind_sign_auth_->ClearCache(); |
| |
| QuicheNotification second; |
| SignedTokenCallback second_callback = |
| [&second](absl::StatusOr<absl::Span<BlindSignToken>> tokens) { |
| EXPECT_THAT(tokens.status().code(), |
| absl::StatusCode::kResourceExhausted); |
| second.Notify(); |
| }; |
| |
| cached_blind_sign_auth_->GetTokens(oauth_token_, num_tokens, |
| ProxyLayer::kProxyA, |
| std::move(second_callback)); |
| second.WaitForNotification(); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quiche |