blob: 92bd9714f44b05f94df746f8c25db231370e0d99 [file] [log] [blame]
// 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 "content/browser/interest_group/interest_group_permissions_cache.h"
#include <array>
#include <string>
#include <vector>
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
// Very short time used by some tests that want to wait until just after a
// timer triggers.
constexpr base::TimeDelta kTinyTime = base::Microseconds(1);
using Permissions = InterestGroupPermissionsCache::Permissions;
class InterestGroupPermissionsCacheTest : public testing::Test {
protected:
// Default values used in most tests.
const url::Origin kFrameOrigin =
url::Origin::Create(GURL("https://frame.test"));
const url::Origin kGroupOrigin =
url::Origin::Create(GURL("https://group.test"));
const net::SchemefulSite kFrameSite = net::SchemefulSite(kFrameOrigin);
const net::NetworkIsolationKey kNetworkIsolationKey =
net::NetworkIsolationKey(kFrameSite, kFrameSite);
const Permissions kPermissions =
Permissions{/*can_join=*/true, /*can_leave=*/false};
// Alternative values when two different values are needed for any field.
const url::Origin kOtherFrameOrigin =
url::Origin::Create(GURL("https://other_frame.test"));
const url::Origin kOtherGroupOrigin =
url::Origin::Create(GURL("https://other_group.test"));
const net::SchemefulSite kOtherFrameSite =
net::SchemefulSite(kOtherFrameOrigin);
const net::NetworkIsolationKey kOtherNetworkIsolationKey =
net::NetworkIsolationKey(kOtherFrameSite, kOtherFrameSite);
const Permissions kOtherPermissions =
Permissions{/*can_join=*/false, /*can_leave=*/true};
base::test::TaskEnvironment task_environment_ = base::test::TaskEnvironment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
InterestGroupPermissionsCache interest_group_permissions_cache_;
};
TEST_F(InterestGroupPermissionsCacheTest, PermissionsEquality) {
EXPECT_EQ(kPermissions, kPermissions);
EXPECT_EQ(kOtherPermissions, kOtherPermissions);
EXPECT_NE(kOtherPermissions, kPermissions);
EXPECT_NE(kPermissions, kOtherPermissions);
// Check that two different Permissions objects that have the same values are
// considered equal.
//
// Same as kPermissions.
Permissions permissions1{/*can_join=*/true, /*can_leave=*/false};
EXPECT_EQ(kPermissions, permissions1);
EXPECT_EQ(permissions1, kPermissions);
// Matches neither `kPermissions` nor `kOtherPermissions`. Used to make sure
// both values matter.
Permissions permissions2{/*can_join=*/false, /*can_leave=*/false};
EXPECT_NE(kPermissions, permissions2);
EXPECT_NE(permissions2, kPermissions);
EXPECT_NE(kOtherPermissions, permissions2);
EXPECT_NE(permissions2, kOtherPermissions);
}
TEST_F(InterestGroupPermissionsCacheTest, Basic) {
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
}
TEST_F(InterestGroupPermissionsCacheTest, Overwrite) {
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
}
TEST_F(InterestGroupPermissionsCacheTest, MultipleEntries) {
// Permissions that are each assigned to a different pair of origins +
// NetworkIsolationKey. This coincidentally covers all distinct permissions
// values, but that is not necessary for this test. They all just need to be
// distinct.
const auto kPermissionsValues = std::to_array<Permissions, 4>({
{/*can_join=*/true, /*can_leave=*/true},
{/*can_join=*/true, /*can_leave=*/false},
{/*can_join=*/false, /*can_leave=*/true},
{/*can_join=*/false, /*can_leave=*/false},
});
// Each set of permissions varies in only one value from the first set. Some
// of these combinations can't actually occur (in particular, the frame origin
// normally corresponds to the NetworkIsolationKey's frame site), but the
// cache layer doesn't care.
interest_group_permissions_cache_.CachePermissions(
kPermissionsValues[0], kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
interest_group_permissions_cache_.CachePermissions(
kPermissionsValues[1], kOtherFrameOrigin, kGroupOrigin,
kNetworkIsolationKey);
interest_group_permissions_cache_.CachePermissions(
kPermissionsValues[2], kFrameOrigin, kOtherGroupOrigin,
kNetworkIsolationKey);
interest_group_permissions_cache_.CachePermissions(kPermissionsValues[3],
kFrameOrigin, kGroupOrigin,
kOtherNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissionsValues[0]);
permissions = interest_group_permissions_cache_.GetPermissions(
kOtherFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissionsValues[1]);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissionsValues[2]);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kOtherNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissionsValues[3]);
}
TEST_F(InterestGroupPermissionsCacheTest, Clear) {
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kOtherFrameOrigin, kOtherGroupOrigin,
kOtherNetworkIsolationKey);
permissions = interest_group_permissions_cache_.GetPermissions(
kOtherFrameOrigin, kOtherGroupOrigin, kOtherNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
EXPECT_EQ(2u, interest_group_permissions_cache_.cache_shards_for_testing());
interest_group_permissions_cache_.Clear();
EXPECT_EQ(0u, interest_group_permissions_cache_.cache_shards_for_testing());
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kOtherFrameOrigin, kOtherGroupOrigin, kOtherNetworkIsolationKey));
}
TEST_F(InterestGroupPermissionsCacheTest, Expiry) {
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
task_environment_.FastForwardBy(
InterestGroupPermissionsCache::kCacheDuration);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
task_environment_.FastForwardBy(kTinyTime);
// Cache shards are only deleted on a timer or on access, neither of which has
// happened yet, though the shard should now be expired.
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
// The expired cache shard should have been deleted on access.
EXPECT_EQ(0u, interest_group_permissions_cache_.cache_shards_for_testing());
}
// Test the case where a CacheEntry expires without the cache shard also
// expiring.
TEST_F(InterestGroupPermissionsCacheTest, CacheEntryExpiry) {
// Create a cache entry.
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Wait until just before the entry expires.
task_environment_.FastForwardBy(
InterestGroupPermissionsCache::kCacheDuration);
// Add another entry to the same cache shard.
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
// There should still be only one cache shard.
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Check that the original entry is still there.
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Wait for the origin entry to expire.
task_environment_.FastForwardBy(kTinyTime);
// Original entry should have expired.
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
// Other entry should still be present.
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
// There should still be only one cache shard.
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
}
TEST_F(InterestGroupPermissionsCacheTest, DeleteExpired) {
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
// Run until just before the delete expired timer runs. Don't call
// GetPermissions(), as it would delete the entry. At this point, the entry is
// expired, but has not been deleted.
task_environment_.FastForwardBy(
InterestGroupPermissionsCache::kDeleteExpiredTimerDuration - kTinyTime);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Run until the delete expired timer runs, which should delete the entry.
task_environment_.FastForwardBy(kTinyTime);
EXPECT_EQ(0u, interest_group_permissions_cache_.cache_shards_for_testing());
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
}
TEST_F(InterestGroupPermissionsCacheTest, DeleteExpiredPreservesUnexpired) {
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Run until just before the delete expired timer runs. Don't call
// GetPermissions(), as it would delete the entry. At this point, the entry is
// expired, but has not been deleted.
task_environment_.FastForwardBy(
InterestGroupPermissionsCache::kDeleteExpiredTimerDuration - kTinyTime);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Add permission for a different group.
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Run until the delete expired timer runs, which should delete nothing.
task_environment_.FastForwardBy(kTinyTime);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// The original permissions should have expired.
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
// But the new permissions should still be around.
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kOtherGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
EXPECT_EQ(1u, interest_group_permissions_cache_.cache_shards_for_testing());
// Run until the delete expired timer triggers again, which should now delete
// the entry.
task_environment_.FastForwardBy(
InterestGroupPermissionsCache::kDeleteExpiredTimerDuration);
EXPECT_EQ(0u, interest_group_permissions_cache_.cache_shards_for_testing());
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey));
}
TEST_F(InterestGroupPermissionsCacheTest, LRU) {
// Fill LRU cache.
std::vector<url::Origin> group_origins;
for (int i = 0; i < InterestGroupPermissionsCache::kMaxCacheEntriesPerShard;
++i) {
url::Origin group_origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i)));
interest_group_permissions_cache_.CachePermissions(
kPermissions, kFrameOrigin, group_origin, kNetworkIsolationKey);
group_origins.emplace_back(std::move(group_origin));
}
// Check all entries are present, accessing in order to end with the same LRU
// order as before.
for (int i = 0; i < InterestGroupPermissionsCache::kMaxCacheEntriesPerShard;
++i) {
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[i], kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
}
// Access first entry.
ASSERT_TRUE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[0], kNetworkIsolationKey));
// Overwrite second entry.
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kFrameOrigin, group_origins[1], kNetworkIsolationKey);
// Add another entry. The third entry is the last-used entry, and should be
// evicted.
interest_group_permissions_cache_.CachePermissions(
kOtherPermissions, kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
// Check third entry was removed.
EXPECT_FALSE(interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[2], kNetworkIsolationKey));
// Check other entries are all still present.
for (int i = 3; i < InterestGroupPermissionsCache::kMaxCacheEntriesPerShard;
++i) {
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[i], kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
}
Permissions* permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[0], kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kPermissions);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, group_origins[1], kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
permissions = interest_group_permissions_cache_.GetPermissions(
kFrameOrigin, kGroupOrigin, kNetworkIsolationKey);
ASSERT_TRUE(permissions);
EXPECT_EQ(*permissions, kOtherPermissions);
}
} // namespace
} // namespace content