| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/first_party_sets/first_party_sets_access_delegate.h" |
| |
| #include <set> |
| #include <string> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "base/version.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/first_party_sets/first_party_set_entry.h" |
| #include "net/first_party_sets/first_party_set_metadata.h" |
| #include "net/first_party_sets/first_party_sets_cache_filter.h" |
| #include "net/first_party_sets/first_party_sets_context_config.h" |
| #include "net/first_party_sets/global_first_party_sets.h" |
| #include "net/first_party_sets/same_party_context.h" |
| #include "services/network/public/mojom/first_party_sets_access_delegate.mojom.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| using ::testing::_; |
| using ::testing::IsEmpty; |
| using ::testing::Not; |
| using ::testing::Optional; |
| using ::testing::Pair; |
| using ::testing::UnorderedElementsAre; |
| using Type = net::SamePartyContext::Type; |
| using OverrideSets = |
| base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>; |
| |
| namespace network { |
| |
| namespace { |
| |
| const net::SchemefulSite kSet1Owner(GURL("https://set1owner.test")); |
| const net::SchemefulSite kSet1Member1(GURL("https://set1member1.test")); |
| const net::SchemefulSite kSet1Member2(GURL("https://set1member2.test")); |
| const net::SchemefulSite kSet2Owner(GURL("https://set2owner.test")); |
| const net::SchemefulSite kSet2Member1(GURL("https://set2member1.test")); |
| const net::SchemefulSite kSet3Owner(GURL("https://set3owner.test")); |
| const net::SchemefulSite kSet3Member1(GURL("https://set3member1.test")); |
| const int64_t kClearAtRunId(2); |
| const int64_t kBrowserRunId(3); |
| |
| mojom::FirstPartySetsAccessDelegateParamsPtr |
| CreateFirstPartySetsAccessDelegateParams(bool enabled) { |
| auto params = mojom::FirstPartySetsAccessDelegateParams::New(); |
| params->enabled = enabled; |
| return params; |
| } |
| |
| mojom::FirstPartySetsReadyEventPtr CreateFirstPartySetsReadyEvent( |
| absl::optional<net::FirstPartySetsContextConfig> config, |
| absl::optional<net::FirstPartySetsCacheFilter> cache_filter) { |
| auto ready_event = mojom::FirstPartySetsReadyEvent::New(); |
| if (config.has_value()) |
| ready_event->config = std::move(config.value()); |
| if (cache_filter.has_value()) |
| ready_event->cache_filter = std::move(cache_filter.value()); |
| return ready_event; |
| } |
| |
| } // namespace |
| |
| // No-op FirstPartySetsAccessDelegate should just pass queries to |
| // FirstPartySetsManager synchronously. |
| class NoopFirstPartySetsAccessDelegateTest : public ::testing::Test { |
| public: |
| NoopFirstPartySetsAccessDelegateTest() |
| : first_party_sets_manager_(/*enabled=*/true), |
| delegate_( |
| /*receiver=*/mojo::NullReceiver(), |
| /*params=*/nullptr, |
| &first_party_sets_manager_) { |
| first_party_sets_manager_.SetCompleteSets(net::GlobalFirstPartySets( |
| base::Version("1.2.3"), |
| /*entries=*/ |
| { |
| {kSet1Member1, net::FirstPartySetEntry( |
| kSet1Owner, net::SiteType::kAssociated, 0)}, |
| {kSet1Member2, net::FirstPartySetEntry( |
| kSet1Owner, net::SiteType::kAssociated, 1)}, |
| {kSet1Owner, |
| net::FirstPartySetEntry(kSet1Owner, net::SiteType::kPrimary, |
| absl::nullopt)}, |
| {kSet2Member1, net::FirstPartySetEntry( |
| kSet2Owner, net::SiteType::kAssociated, 0)}, |
| {kSet2Owner, |
| net::FirstPartySetEntry(kSet2Owner, net::SiteType::kPrimary, |
| absl::nullopt)}, |
| }, |
| /*aliases=*/{})); |
| } |
| |
| FirstPartySetsAccessDelegate& delegate() { return delegate_; } |
| |
| private: |
| FirstPartySetsManager first_party_sets_manager_; |
| FirstPartySetsAccessDelegate delegate_; |
| }; |
| |
| TEST_F(NoopFirstPartySetsAccessDelegateTest, ComputeMetadata) { |
| EXPECT_THAT( |
| delegate() |
| .ComputeMetadata(kSet1Member1, &kSet1Owner, |
| {kSet1Member1, kSet1Owner}, base::NullCallback()) |
| ->context(), |
| net::SamePartyContext(Type::kSameParty)); |
| } |
| |
| TEST_F(NoopFirstPartySetsAccessDelegateTest, FindEntries) { |
| EXPECT_THAT( |
| delegate().FindEntries({kSet1Member1, kSet2Member1}, |
| base::NullCallback()), |
| FirstPartySetsAccessDelegate::EntriesResult({ |
| {kSet1Member1, |
| net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)}, |
| {kSet2Member1, |
| net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)}, |
| })); |
| } |
| |
| TEST_F(NoopFirstPartySetsAccessDelegateTest, GetCacheFilterMatchInfo) { |
| EXPECT_EQ( |
| delegate().GetCacheFilterMatchInfo(kSet1Member1, base::NullCallback()), |
| net::FirstPartySetsCacheFilter::MatchInfo()); |
| } |
| |
| class FirstPartySetsAccessDelegateTest : public ::testing::Test { |
| public: |
| explicit FirstPartySetsAccessDelegateTest(bool enabled) |
| : first_party_sets_manager_(/*enabled=*/true), |
| delegate_(delegate_remote_.BindNewPipeAndPassReceiver(), |
| CreateFirstPartySetsAccessDelegateParams(enabled), |
| &first_party_sets_manager_) { |
| first_party_sets_manager_.SetCompleteSets(net::GlobalFirstPartySets( |
| base::Version("1.2.3"), |
| /*entries=*/ |
| { |
| {kSet1Member1, net::FirstPartySetEntry( |
| kSet1Owner, net::SiteType::kAssociated, 0)}, |
| {kSet1Member2, net::FirstPartySetEntry( |
| kSet1Owner, net::SiteType::kAssociated, 1)}, |
| {kSet1Owner, |
| net::FirstPartySetEntry(kSet1Owner, net::SiteType::kPrimary, |
| absl::nullopt)}, |
| {kSet2Member1, net::FirstPartySetEntry( |
| kSet2Owner, net::SiteType::kAssociated, 0)}, |
| {kSet2Owner, |
| net::FirstPartySetEntry(kSet2Owner, net::SiteType::kPrimary, |
| absl::nullopt)}, |
| }, |
| /*aliases=*/{})); |
| } |
| |
| net::FirstPartySetMetadata ComputeMetadataAndWait( |
| const net::SchemefulSite& site, |
| const net::SchemefulSite* top_frame_site, |
| const std::set<net::SchemefulSite>& party_context) { |
| base::test::TestFuture<net::FirstPartySetMetadata> future; |
| absl::optional<net::FirstPartySetMetadata> result = |
| delegate_.ComputeMetadata(site, top_frame_site, party_context, |
| future.GetCallback()); |
| return result.has_value() ? std::move(result).value() : future.Take(); |
| } |
| |
| FirstPartySetsAccessDelegate::EntriesResult FindEntriesAndWait( |
| const base::flat_set<net::SchemefulSite>& site) { |
| base::test::TestFuture<FirstPartySetsAccessDelegate::EntriesResult> future; |
| absl::optional<FirstPartySetsAccessDelegate::EntriesResult> result = |
| delegate_.FindEntries(site, future.GetCallback()); |
| return result.has_value() ? result.value() : future.Get(); |
| } |
| |
| net::FirstPartySetsCacheFilter::MatchInfo GetCacheFilterMatchInfoAndWait( |
| const net::SchemefulSite& site) { |
| base::test::TestFuture<net::FirstPartySetsCacheFilter::MatchInfo> future; |
| absl::optional<net::FirstPartySetsCacheFilter::MatchInfo> result = |
| delegate_.GetCacheFilterMatchInfo(site, future.GetCallback()); |
| return result.has_value() ? result.value() : future.Get(); |
| } |
| |
| FirstPartySetsAccessDelegate& delegate() { return delegate_; } |
| |
| mojom::FirstPartySetsAccessDelegate* delegate_remote() { |
| return delegate_remote_.get(); |
| } |
| |
| private: |
| base::test::TaskEnvironment env_; |
| FirstPartySetsManager first_party_sets_manager_; |
| mojo::Remote<mojom::FirstPartySetsAccessDelegate> delegate_remote_; |
| FirstPartySetsAccessDelegate delegate_; |
| }; |
| |
| // Since the FPSs is disabled for the context, none of the callbacks |
| // should ever be called, and the return values should all be non-nullopt. |
| class FirstPartySetsAccessDelegateDisabledTest |
| : public FirstPartySetsAccessDelegateTest { |
| public: |
| FirstPartySetsAccessDelegateDisabledTest() |
| : FirstPartySetsAccessDelegateTest(false) {} |
| }; |
| |
| TEST_F(FirstPartySetsAccessDelegateDisabledTest, ComputeMetadata) { |
| // Same as the default ctor, but just to be explicit: |
| net::FirstPartySetMetadata expected_metadata(net::SamePartyContext(), |
| /*frame_entry=*/nullptr, |
| /*top_frame_entry=*/nullptr); |
| |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| expected_metadata); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateDisabledTest, FindEntries) { |
| EXPECT_THAT(FindEntriesAndWait({kSet1Member1, kSet2Member1}), IsEmpty()); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateDisabledTest, GetCacheFilterMatchInfo) { |
| EXPECT_THAT( |
| delegate().GetCacheFilterMatchInfo(kSet1Member1, base::NullCallback()), |
| Optional(net::FirstPartySetsCacheFilter::MatchInfo())); |
| } |
| |
| // Test fixture that allows precise control over when the instance gets FPS |
| // data. Useful for testing async flows. |
| class AsyncFirstPartySetsAccessDelegateTest |
| : public FirstPartySetsAccessDelegateTest { |
| public: |
| AsyncFirstPartySetsAccessDelegateTest() |
| : FirstPartySetsAccessDelegateTest(true) {} |
| }; |
| |
| TEST_F(AsyncFirstPartySetsAccessDelegateTest, |
| QueryBeforeReady_ComputeMetadata) { |
| base::test::TestFuture<net::FirstPartySetMetadata> future; |
| { |
| // Force deallocation to provoke a UAF if the impl just copies the pointer. |
| net::SchemefulSite local_member1(kSet1Member1); |
| EXPECT_FALSE(delegate().ComputeMetadata( |
| kSet1Member1, &local_member1, {kSet1Member1}, future.GetCallback())); |
| } |
| |
| delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| |
| net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated, 0); |
| EXPECT_EQ(future.Get(), |
| net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty), |
| &entry, &entry)); |
| } |
| |
| TEST_F(AsyncFirstPartySetsAccessDelegateTest, QueryBeforeReady_FindEntries) { |
| base::test::TestFuture<FirstPartySetsAccessDelegate::EntriesResult> future; |
| EXPECT_FALSE(delegate().FindEntries({kSet1Member1, kSet2Member1}, |
| future.GetCallback())); |
| |
| delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| |
| EXPECT_THAT( |
| future.Get(), |
| FirstPartySetsAccessDelegate::EntriesResult({ |
| {kSet1Member1, |
| net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)}, |
| {kSet2Member1, |
| net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)}, |
| })); |
| } |
| |
| TEST_F(AsyncFirstPartySetsAccessDelegateTest, |
| QueryBeforeReady_GetCacheFilterMatchInfo) { |
| base::test::TestFuture<net::FirstPartySetsCacheFilter::MatchInfo> future; |
| EXPECT_FALSE( |
| delegate().GetCacheFilterMatchInfo(kSet1Owner, future.GetCallback())); |
| |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| /*config=*/absl::nullopt, |
| net::FirstPartySetsCacheFilter({{kSet1Owner, kClearAtRunId}}, |
| kBrowserRunId))); |
| |
| net::FirstPartySetsCacheFilter::MatchInfo match_info; |
| match_info.clear_at_run_id = kClearAtRunId; |
| match_info.browser_run_id = kBrowserRunId; |
| EXPECT_EQ(future.Get(), match_info); |
| } |
| |
| TEST_F(AsyncFirstPartySetsAccessDelegateTest, OverrideSets_ComputeMetadata) { |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| net::FirstPartySetsContextConfig({ |
| {kSet1Member1, |
| {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, |
| 0)}}, |
| {kSet3Owner, |
| {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary, |
| absl::nullopt)}}, |
| }), |
| /*cache_filter-*/ absl::nullopt)); |
| |
| net::FirstPartySetEntry primary_entry(kSet3Owner, net::SiteType::kPrimary, |
| absl::nullopt); |
| net::FirstPartySetEntry associated_entry(kSet3Owner, |
| net::SiteType::kAssociated, 0); |
| EXPECT_EQ(ComputeMetadataAndWait(kSet3Owner, &kSet1Member1, {kSet1Member1}), |
| net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty), |
| &primary_entry, &associated_entry)); |
| } |
| |
| TEST_F(AsyncFirstPartySetsAccessDelegateTest, OverrideSets_FindEntries) { |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| net::FirstPartySetsContextConfig({ |
| {kSet3Owner, |
| {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary, |
| absl::nullopt)}}, |
| }), |
| /*cache_filter-*/ absl::nullopt)); |
| |
| EXPECT_THAT(FindEntriesAndWait({kSet3Owner}), |
| UnorderedElementsAre(Pair(kSet3Owner, _))); |
| } |
| |
| class SyncFirstPartySetsAccessDelegateTest |
| : public AsyncFirstPartySetsAccessDelegateTest { |
| public: |
| SyncFirstPartySetsAccessDelegateTest() { |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| net::FirstPartySetsContextConfig({ |
| {kSet3Member1, |
| {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, |
| 0)}}, |
| {kSet3Owner, |
| {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary, |
| absl::nullopt)}}, |
| }), |
| net::FirstPartySetsCacheFilter({{kSet1Owner, kClearAtRunId}}, |
| kBrowserRunId))); |
| } |
| }; |
| |
| TEST_F(SyncFirstPartySetsAccessDelegateTest, ComputeMetadata) { |
| net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated, 0); |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, {kSet1Member1}), |
| net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty), |
| &entry, &entry)); |
| } |
| |
| TEST_F(SyncFirstPartySetsAccessDelegateTest, FindEntries) { |
| EXPECT_THAT( |
| FindEntriesAndWait({kSet1Member1, kSet2Member1, kSet3Member1}), |
| FirstPartySetsAccessDelegate::EntriesResult({ |
| {kSet1Member1, |
| net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)}, |
| {kSet2Member1, |
| net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)}, |
| {kSet3Member1, |
| net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, 0)}, |
| })); |
| } |
| |
| TEST_F(SyncFirstPartySetsAccessDelegateTest, GetCacheFilterMatchInfo) { |
| net::FirstPartySetsCacheFilter::MatchInfo match_info; |
| match_info.clear_at_run_id = kClearAtRunId; |
| match_info.browser_run_id = kBrowserRunId; |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Owner), match_info); |
| } |
| |
| // Verifies the behaviors of the delegate when First-Party Sets are initially |
| // enabled but disabled later on. |
| // Queries should only be deferred if they arrive when the delegate is enabled |
| // and NotifyReady hasn't been called yet. |
| class FirstPartySetsAccessDelegateSetToDisabledTest |
| : public FirstPartySetsAccessDelegateTest { |
| public: |
| FirstPartySetsAccessDelegateSetToDisabledTest() |
| : FirstPartySetsAccessDelegateTest(/*enabled=*/true) {} |
| }; |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| DisabledThenReady_ComputeMetadata) { |
| net::FirstPartySetMetadata empty_metadata(net::SamePartyContext(), |
| /*frame_entry=*/nullptr, |
| /*top_frame_entry=*/nullptr); |
| |
| base::test::TestFuture<net::FirstPartySetMetadata> future; |
| EXPECT_FALSE(delegate().ComputeMetadata(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}, |
| future.GetCallback())); |
| |
| delegate().SetEnabled(false); |
| |
| // All queries received when the delegate is disabled receive empty responses. |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| |
| // Queries received when the delegate is enabled receive non-empty responses |
| // once the config is ready. |
| EXPECT_NE(future.Take(), empty_metadata); |
| |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| DisabledThenReady_FindEntries) { |
| base::test::TestFuture< |
| base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>> |
| future; |
| EXPECT_FALSE(delegate().FindEntries({kSet1Member1}, future.GetCallback())); |
| |
| delegate().SetEnabled(false); |
| |
| // All queries received when the delegate is disabled receive empty responses. |
| EXPECT_THAT(FindEntriesAndWait({kSet1Member1}), IsEmpty()); |
| |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| |
| // Queries received when the delegate is enabled receive non-empty responses |
| // once the config is ready. |
| EXPECT_THAT(future.Take(), Not(IsEmpty())); |
| |
| EXPECT_THAT(FindEntriesAndWait({kSet1Member1}), IsEmpty()); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| DisabledThenReady_GetCacheFilterMatchInfo) { |
| net::FirstPartySetsCacheFilter::MatchInfo match_info; |
| |
| base::test::TestFuture<net::FirstPartySetsCacheFilter::MatchInfo> future; |
| EXPECT_FALSE( |
| delegate().GetCacheFilterMatchInfo(kSet1Member1, future.GetCallback())); |
| |
| delegate().SetEnabled(false); |
| |
| // All queries received when the delegate is disabled receive empty responses. |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Member1), match_info); |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| EXPECT_TRUE(future.Wait()); |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Member1), match_info); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| ReadyThenDisabled_ComputeMetadata) { |
| base::test::TestFuture<net::FirstPartySetMetadata> future; |
| EXPECT_FALSE(delegate().ComputeMetadata(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}, |
| future.GetCallback())); |
| |
| delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| |
| net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated, 0); |
| EXPECT_EQ(future.Get(), |
| net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty), |
| &entry, &entry)); |
| |
| ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}); |
| |
| delegate().SetEnabled(false); |
| net::FirstPartySetMetadata empty_metadata(net::SamePartyContext(), |
| /*frame_entry=*/nullptr, |
| /*top_frame_entry=*/nullptr); |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| ReadyThenDisabled_FindEntries) { |
| base::test::TestFuture<FirstPartySetsAccessDelegate::EntriesResult> future; |
| |
| EXPECT_FALSE(delegate().FindEntries({kSet1Member1}, future.GetCallback())); |
| delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| EXPECT_THAT( |
| future.Get(), |
| FirstPartySetsAccessDelegate::EntriesResult( |
| {{kSet1Member1, net::FirstPartySetEntry( |
| kSet1Owner, net::SiteType::kAssociated, 0)}})); |
| FindEntriesAndWait({kSet1Member1}); |
| |
| delegate().SetEnabled(false); |
| EXPECT_THAT(FindEntriesAndWait({kSet1Member1}), IsEmpty()); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToDisabledTest, |
| ReadyThenDisabled_GetCacheFilterMatchInfo) { |
| net::FirstPartySetsCacheFilter::MatchInfo match_info; |
| base::test::TestFuture<net::FirstPartySetsCacheFilter::MatchInfo> future; |
| EXPECT_FALSE( |
| delegate().GetCacheFilterMatchInfo(kSet1Member1, future.GetCallback())); |
| |
| delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| EXPECT_EQ(future.Get(), match_info); |
| |
| delegate().SetEnabled(false); |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Member1), match_info); |
| } |
| |
| // Verifies the behaviors of the delegate when First-Party Sets are initially |
| // disabled but enabled later on. |
| // Queries should only be deferred if they arrive when the delegate is enabled |
| // and NotifyReady hasn't been called yet. |
| class FirstPartySetsAccessDelegateSetToEnabledTest |
| : public FirstPartySetsAccessDelegateTest { |
| public: |
| FirstPartySetsAccessDelegateSetToEnabledTest() |
| : FirstPartySetsAccessDelegateTest(/*enabled=*/false) {} |
| }; |
| |
| // This scenario might not be reproducible in production code but it's worth |
| // testing nonetheless. We may add metrics to observe how often this case |
| // occurs. |
| TEST_F(FirstPartySetsAccessDelegateSetToEnabledTest, |
| EnabledThenReady_ComputeMetadata) { |
| net::FirstPartySetMetadata empty_metadata(net::SamePartyContext(), |
| /*frame_entry=*/nullptr, |
| /*top_frame_entry=*/nullptr); |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| |
| delegate().SetEnabled(true); |
| |
| base::test::TestFuture<net::FirstPartySetMetadata> future; |
| net::FirstPartySetEntry primary_entry(kSet2Owner, net::SiteType::kPrimary, |
| absl::nullopt); |
| net::FirstPartySetEntry associated_entry(kSet2Owner, |
| net::SiteType::kAssociated, 0); |
| EXPECT_FALSE(delegate().ComputeMetadata( |
| kSet2Owner, &kSet1Member1, {kSet1Member1}, future.GetCallback())); |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| net::FirstPartySetsContextConfig( |
| {{kSet1Member1, |
| {net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, |
| 0)}}}), |
| /*cache_filter-*/ absl::nullopt)); |
| EXPECT_EQ(future.Get(), |
| net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty), |
| &primary_entry, &associated_entry)); |
| ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToEnabledTest, |
| EnabledThenReady_FindEntries) { |
| EXPECT_EQ(FindEntriesAndWait({kSet1Member1}), |
| FirstPartySetsAccessDelegate::EntriesResult()); |
| |
| delegate().SetEnabled(true); |
| |
| base::test::TestFuture<FirstPartySetsAccessDelegate::EntriesResult> future; |
| EXPECT_FALSE(delegate().FindEntries({kSet1Member1}, future.GetCallback())); |
| delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent( |
| net::FirstPartySetsContextConfig( |
| {{kSet1Member1, |
| {net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, |
| 0)}}}), |
| /*cache_filter-*/ absl::nullopt)); |
| EXPECT_EQ( |
| future.Get(), |
| FirstPartySetsAccessDelegate::EntriesResult( |
| {{kSet1Member1, net::FirstPartySetEntry( |
| kSet2Owner, net::SiteType::kAssociated, 0)}})); |
| FindEntriesAndWait({kSet1Member1}); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToEnabledTest, |
| ReadyThenEnabled_ComputeMetadata) { |
| net::FirstPartySetMetadata empty_metadata(net::SamePartyContext(), |
| /*frame_entry=*/nullptr, |
| /*top_frame_entry=*/nullptr); |
| EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| delegate().SetEnabled(true); |
| |
| EXPECT_NE(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, |
| {kSet1Member1, kSet1Owner}), |
| empty_metadata); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToEnabledTest, |
| ReadyThenEnabled_FindEntries) { |
| EXPECT_EQ(FindEntriesAndWait({kSet1Member1}), |
| FirstPartySetsAccessDelegate::EntriesResult()); |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| delegate().SetEnabled(true); |
| |
| EXPECT_THAT(FindEntriesAndWait({kSet1Member1}), |
| UnorderedElementsAre(Pair(kSet1Member1, _))); |
| } |
| |
| TEST_F(FirstPartySetsAccessDelegateSetToEnabledTest, |
| ReadyThenEnabled_GetCacheFilterMatchInfo) { |
| net::FirstPartySetsCacheFilter::MatchInfo match_info; |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Member1), match_info); |
| delegate().NotifyReady(mojom::FirstPartySetsReadyEvent::New()); |
| delegate().SetEnabled(true); |
| |
| EXPECT_EQ(GetCacheFilterMatchInfoAndWait(kSet1Member1), match_info); |
| } |
| |
| } // namespace network |