blob: 3f1893835494b8fa5597ebb41cce50bf27935cb8 [file] [log] [blame]
// 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