blob: ea1e7ace23721adca1060868707941fc3fa80868 [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/media/media_license_manager.h"
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_piece_forward.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "components/services/storage/public/cpp/constants.h"
#include "content/browser/media/media_license_quota_client.h"
#include "content/public/browser/storage_partition.h"
#include "media/cdm/cdm_type.h"
#include "media/mojo/mojom/cdm_storage.mojom-forward.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
namespace {
const media::CdmType kCdmType{1234, 5678};
const char kExampleOrigin[] = "https://example.com";
} // namespace
class MediaLicenseManagerTest : public testing::Test {
public:
MediaLicenseManagerTest() : in_memory_(false) {}
explicit MediaLicenseManagerTest(bool in_memory) : in_memory_(in_memory) {}
void SetUp() override {
ASSERT_TRUE(profile_path_.CreateUniqueTempDir());
quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
in_memory_, in_memory_ ? base::FilePath() : profile_path_.GetPath(),
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
/*special storage policy=*/nullptr);
quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
static_cast<storage::MockQuotaManager*>(quota_manager_.get()),
base::SingleThreadTaskRunner::GetCurrentDefault());
manager_ = std::make_unique<MediaLicenseManager>(
in_memory_,
/*special storage policy=*/nullptr, quota_manager_proxy_);
}
void TearDown() override {
// Let the client go away before dropping a ref of the quota manager proxy.
quota_manager_ = nullptr;
quota_manager_proxy_ = nullptr;
task_environment_.RunUntilIdle();
EXPECT_TRUE(profile_path_.Delete());
}
// Hard-coded to the default bucket, since this API should never be used in
// non-default buckets anyways.
storage::BucketLocator GetOrCreateBucket(
const blink::StorageKey& storage_key) {
base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>> future;
quota_manager_->UpdateOrCreateBucket(
storage::BucketInitParams::ForDefaultBucket(storage_key),
future.GetCallback());
auto bucket = future.Take();
EXPECT_TRUE(bucket.has_value());
return bucket->ToBucketLocator();
}
mojo::AssociatedRemote<media::mojom::CdmFile> OpenCdmFile(
const mojo::Remote<media::mojom::CdmStorage>& storage,
const std::string& file_name) {
mojo::AssociatedRemote<media::mojom::CdmFile> cdm_file;
base::test::TestFuture<media::mojom::CdmStorage::Status,
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
open_future;
storage->Open(file_name, open_future.GetCallback());
auto result = open_future.Take();
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kSuccess);
cdm_file.Bind(std::move(std::get<1>(result)));
return cdm_file;
}
void Write(const mojo::AssociatedRemote<media::mojom::CdmFile>& cdm_file,
const std::string& data) {
base::test::TestFuture<media::mojom::CdmFile::Status> write_future;
cdm_file->Write(
std::vector<uint8_t>(data.data(), data.data() + data.size()),
write_future.GetCallback());
EXPECT_EQ(write_future.Get(), media::mojom::CdmFile::Status::kSuccess);
}
// Reads the previously opened `cdm_file` and check that its contents match
// `expected_data`.
void ExpectFileContents(
const mojo::AssociatedRemote<media::mojom::CdmFile>& cdm_file,
const base::StringPiece expected_data) {
base::test::TestFuture<media::mojom::CdmFile::Status, std::vector<uint8_t>>
future;
cdm_file->Read(future.GetCallback<media::mojom::CdmFile::Status,
const std::vector<uint8_t>&>());
media::mojom::CdmFile::Status status = future.Get<0>();
auto data = future.Get<1>();
EXPECT_EQ(status, media::mojom::CdmFile::Status::kSuccess);
EXPECT_EQ(base::StringPiece(reinterpret_cast<const char*>(data.data()),
data.size()),
expected_data);
}
base::FilePath FindMediaLicenseDatabase() {
base::FileEnumerator file_enumerator(profile_path_.GetPath(),
/*recursive=*/true,
base::FileEnumerator::FILES);
base::FilePath file;
while (!(file = file_enumerator.Next()).empty()) {
if (file.BaseName().value() == storage::kMediaLicenseDatabaseFileName) {
return file;
}
}
return base::FilePath();
}
protected:
const bool in_memory_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
// This must be above MediaLicenseManager, to ensure that no file is accessed
// when the temporary directory is deleted.
base::ScopedTempDir profile_path_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<MediaLicenseManager> manager_;
};
TEST_F(MediaLicenseManagerTest, DeleteBucketData) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
// Write some data.
Write(cdm_file, kTestData);
auto database_file = FindMediaLicenseDatabase();
EXPECT_FALSE(database_file.empty());
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
// Confirm that the database was deleted, but the Media License directory was
// not.
EXPECT_FALSE(base::PathExists(database_file));
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
// Confirm that the file is now empty.
ExpectFileContents(cdm_file, "");
}
TEST_F(MediaLicenseManagerTest, DeleteBucketDataClosedStorage) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
Write(cdm_file, kTestData);
auto database_file = FindMediaLicenseDatabase();
EXPECT_FALSE(database_file.empty());
// We should still be able to wipe data to a closed storage.
cdm_file.reset();
remote.reset();
EXPECT_TRUE(base::PathExists(database_file));
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
// Confirm that the database was deleted, but the Media License
// directory was not.
EXPECT_FALSE(base::PathExists(database_file));
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
}
TEST_F(MediaLicenseManagerTest, DeleteBucketDataOpenConnection) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
Write(cdm_file, kTestData);
auto database_file = FindMediaLicenseDatabase();
EXPECT_FALSE(database_file.empty());
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
// Confirm that the database was deleted, but the Media License directory was
// not.
EXPECT_FALSE(base::PathExists(database_file));
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
// Confirm that the file is now empty.
ExpectFileContents(cdm_file, "");
// Write some more data. This should succeed.
Write(cdm_file, kTestData);
EXPECT_TRUE(base::PathExists(database_file));
EXPECT_TRUE(base::PathExists(database_file.DirName()));
}
TEST_F(MediaLicenseManagerTest, BucketCreationFailed) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Disable the quota database, causing GetOrCreateBucket() to fail.
quota_manager_->SetDisableDatabase(/*disable=*/true);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
// Opening a CDM file should fail.
base::test::TestFuture<media::mojom::CdmStorage::Status,
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
open_future;
remote->Open("test_file", open_future.GetCallback());
auto result = open_future.Take();
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kFailure);
EXPECT_FALSE(std::get<1>(result).is_valid());
}
class MediaLicenseManagerIncognitoTest : public MediaLicenseManagerTest {
public:
MediaLicenseManagerIncognitoTest()
: MediaLicenseManagerTest(/*in_memory=*/true) {}
};
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketData) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
// Write some data.
Write(cdm_file, kTestData);
// We should be able to read the written file.
ExpectFileContents(cdm_file, kTestData);
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
// Confirm that the file is now empty.
ExpectFileContents(cdm_file, "");
}
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketDataClosedStorage) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
Write(cdm_file, kTestData);
// There should be no db_file since its in memory.
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
// We should still be able to wipe data to a closed storage.
cdm_file.reset();
remote.reset();
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
}
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketDataOpenConnection) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
auto cdm_file = OpenCdmFile(remote, "test_file");
Write(cdm_file, kTestData);
ExpectFileContents(cdm_file, kTestData);
// There should be no db file since its in memory.
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
// Delete data for this storage key.
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
// Confirm that the file is now empty.
ExpectFileContents(cdm_file, "");
// Write some more data. This should succeed.
Write(cdm_file, kTestData);
// Check that no file was created.
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
}
TEST_F(MediaLicenseManagerIncognitoTest, BucketCreationFailed) {
const std::string kTestData("Test Data");
mojo::Remote<media::mojom::CdmStorage> remote;
blink::StorageKey storage_key =
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
storage::BucketLocator bucket = GetOrCreateBucket(storage_key);
MediaLicenseManager::BindingContext binding_context(storage_key, kCdmType);
// Disable the quota database, causing GetOrCreateBucket() to fail.
quota_manager_->SetDisableDatabase(/*disable=*/true);
// Open CDM storage for a storage key.
manager_->OpenCdmStorage(binding_context,
remote.BindNewPipeAndPassReceiver());
// Opening a CDM file should fail.
base::test::TestFuture<media::mojom::CdmStorage::Status,
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
open_future;
remote->Open("test_file", open_future.GetCallback());
auto result = open_future.Take();
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kFailure);
EXPECT_FALSE(std::get<1>(result).is_valid());
}
} // namespace content