blob: 13968cd1990def2d692f8cc5a2c19ee377708f24 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/media/android/cdm/media_drm_origin_id_manager.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "base/unguessable_token.h"
#include "base/value_conversions.h"
#include "chrome/browser/media/android/cdm/media_drm_origin_id_manager_factory.h"
#include "chrome/test/base/testing_profile.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/base/android/media_drm_bridge.h"
#include "media/base/media_switches.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace {
using testing::Return;
using MediaDrmOriginId = MediaDrmOriginIdManager::MediaDrmOriginId;
// These values must match the values specified for the implementation
// in media_drm_origin_id_manager.cc.
const char kMediaDrmOriginIds[] = "media.media_drm_origin_ids";
const char kExpirableToken[] = "expirable_token";
const char kAvailableOriginIds[] = "origin_ids";
constexpr size_t kExpectedPreferenceListSize = 2;
constexpr base::TimeDelta kExpirationDelta = base::TimeDelta::FromHours(24);
constexpr size_t kConnectionAttempts = 5;
constexpr base::TimeDelta kStartupDelay = base::TimeDelta::FromMinutes(1);
} // namespace
class MediaDrmOriginIdManagerTest : public testing::Test {
public:
// By default MediaDrmOriginIdManager will attempt to pre-provision origin
// IDs at startup. For most tests this should be disabled.
void Initialize(bool enable_preprovision_at_startup = false) {
scoped_feature_list_.InitWithFeatureState(
media::kMediaDrmPreprovisioningAtStartup,
enable_preprovision_at_startup);
TestingProfile::Builder profile_builder;
profile_ = profile_builder.Build();
origin_id_manager_ =
MediaDrmOriginIdManagerFactory::GetForProfile(profile_.get());
origin_id_manager_->SetProvisioningResultCBForTesting(
base::BindRepeating(&MediaDrmOriginIdManagerTest::GetProvisioningResult,
base::Unretained(this)));
}
MOCK_METHOD0(GetProvisioningResult, bool());
// Call MediaDrmOriginIdManager::GetOriginId() synchronously.
MediaDrmOriginId GetOriginId() {
base::RunLoop run_loop;
MediaDrmOriginId result;
origin_id_manager_->GetOriginId(base::BindOnce(
[](base::OnceClosure callback, MediaDrmOriginId* result, bool success,
const MediaDrmOriginId& origin_id) {
// If |success| = true, then |origin_id| should be not null.
// If |success| = false, then |origin_id| should be null.
EXPECT_EQ(success, origin_id.has_value());
*result = origin_id;
std::move(callback).Run();
},
run_loop.QuitClosure(), &result));
run_loop.Run();
return result;
}
void PreProvision() {
origin_id_manager_->PreProvisionIfNecessary();
}
std::string DisplayPref(const base::Value* value) {
std::string output;
JSONStringValueSerializer serializer(&output);
EXPECT_TRUE(serializer.Serialize(*value));
return output;
}
const PrefService::Preference* FindPreference(const std::string& path) const {
return profile_->GetTestingPrefService()->FindPreference(path);
}
const base::DictionaryValue* GetDictionary(const std::string& path) const {
return profile_->GetTestingPrefService()->GetDictionary(path);
}
// On devices that support per-application provisioning pre-provisioning
// should fully populate the list of pre-provisioned origin IDs (as long as
// provisioning succeeds). On devices that don't the list should be empty.
void CheckPreferenceForPreProvisioning() {
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* pref = FindPreference(kMediaDrmOriginIds);
EXPECT_TRUE(pref);
EXPECT_EQ(kMediaDrmOriginIds, pref->name());
EXPECT_EQ(base::Value::Type::DICTIONARY, pref->GetType());
auto* dict = pref->GetValue();
EXPECT_TRUE(dict->is_dict());
DVLOG(1) << DisplayPref(pref->GetValue());
auto* list = dict->FindKey(kAvailableOriginIds);
if (media::MediaDrmBridge::IsPerApplicationProvisioningSupported()) {
// PreProvision() should have pre-provisioned
// |kExpectedPreferenceListSize| origin IDs.
DVLOG(1) << "Per-application provisioning is supported.";
EXPECT_TRUE(list->is_list());
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
} else {
// No pre-provisioned origin IDs should exist. In fact, the dictionary
// should not have any entries.
DVLOG(1) << "Per-application provisioning is NOT supported.";
EXPECT_FALSE(list);
EXPECT_EQ(dict->DictSize(), 0u);
}
}
protected:
content::TestBrowserThreadBundle test_browser_thread_bundle_{
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
base::test::ScopedTaskEnvironment::NowSource::MAIN_THREAD_MOCK_TIME};
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TestingProfile> profile_;
MediaDrmOriginIdManager* origin_id_manager_;
};
TEST_F(MediaDrmOriginIdManagerTest, DisablePreProvisioningAtStartup) {
// Test verifies that the construction of MediaDrmOriginIdManager is
// successful. Pre-provisioning origin IDs at startup should be disabled
// so no calls to GetProvisioningResult() are expected.
Initialize();
EXPECT_FALSE(
base::FeatureList::IsEnabled(media::kMediaDrmPreprovisioningAtStartup));
EXPECT_FALSE(
base::FeatureList::IsEnabled(media::kFailUrlProvisionFetcherForTesting));
test_browser_thread_bundle_.RunUntilIdle();
// Preference should not exist. Not using GetDictionary() as it will
// create the preference if it doesn't exist.
EXPECT_FALSE(
profile_->GetTestingPrefService()->HasPrefPath(kMediaDrmOriginIds));
}
TEST_F(MediaDrmOriginIdManagerTest, OneOriginId) {
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize();
EXPECT_TRUE(GetOriginId());
}
TEST_F(MediaDrmOriginIdManagerTest, TwoOriginIds) {
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize();
MediaDrmOriginId origin_id1 = GetOriginId();
MediaDrmOriginId origin_id2 = GetOriginId();
EXPECT_TRUE(origin_id1);
EXPECT_TRUE(origin_id2);
EXPECT_NE(origin_id1, origin_id2);
}
TEST_F(MediaDrmOriginIdManagerTest, PreProvision) {
// On devices that support per-application provisioning PreProvision() will
// pre-provisioned several origin IDs and populate the preference. On devices
// that don't, the list will be empty.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize();
PreProvision();
test_browser_thread_bundle_.RunUntilIdle();
CheckPreferenceForPreProvisioning();
}
TEST_F(MediaDrmOriginIdManagerTest, PreProvisionAtStartup) {
// Initialize without disabling kMediaDrmPreprovisioningAtStartup. Check
// that pre-provisioning actually runs at profile creation (on devices
// that support it).
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize(true);
DVLOG(1) << "Advancing Time";
test_browser_thread_bundle_.FastForwardBy(kStartupDelay);
test_browser_thread_bundle_.RunUntilIdle();
CheckPreferenceForPreProvisioning();
}
TEST_F(MediaDrmOriginIdManagerTest, PreProvisionFailAtStartup) {
// Initialize without disabling kMediaDrmPreprovisioningAtStartup. Have
// provisioning fail at startup, if it is attempted.
if (media::MediaDrmBridge::IsPerApplicationProvisioningSupported()) {
EXPECT_CALL(*this, GetProvisioningResult()).WillOnce(Return(false));
} else {
// If per-application provisioning is NOT supported, no attempt will be made
// to pre-provision any origin IDs at startup.
EXPECT_CALL(*this, GetProvisioningResult()).Times(0);
}
Initialize(true);
DVLOG(1) << "Advancing Time";
test_browser_thread_bundle_.FastForwardBy(kStartupDelay);
test_browser_thread_bundle_.RunUntilIdle();
// Pre-provisioning should have failed.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
// After failure the preference should not contain |kExpireableToken| as that
// should only be set if the user requested an origin ID on devices that
// support per-application provisioning.
EXPECT_FALSE(dict->FindKey(kExpirableToken));
// There should be no pre-provisioned origin IDs.
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Now let provisioning succeed.
if (media::MediaDrmBridge::IsPerApplicationProvisioningSupported()) {
// If per-application provisioning is NOT supported, no attempt will be made
// to pre-provision any origin IDs. So only expect calls if per-application
// provisioning is supported.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
}
// Trigger a network connection to force pre-provisioning to run again.
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
test_browser_thread_bundle_.RunUntilIdle();
// Pre-provisioning should have run again. Should return the same result as if
// pre-provisioning had succeeded at startup.
CheckPreferenceForPreProvisioning();
}
TEST_F(MediaDrmOriginIdManagerTest, GetOriginIdCreatesList) {
// After fetching an origin ID the code should pre-provision more origins
// and fill up the list. This is independent of whether the device supports
// per-application provisioning or not.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize();
GetOriginId();
test_browser_thread_bundle_.RunUntilIdle();
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* pref = FindPreference(kMediaDrmOriginIds);
EXPECT_TRUE(pref);
auto* dict = pref->GetValue();
EXPECT_TRUE(dict->is_dict());
DVLOG(1) << DisplayPref(pref->GetValue());
auto* list = dict->FindKey(kAvailableOriginIds);
EXPECT_TRUE(list->is_list());
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
}
TEST_F(MediaDrmOriginIdManagerTest, OriginIdNotInList) {
// After fetching one origin ID MediaDrmOriginIdManager will create the list
// of pre-provisioned origin IDs (asynchronously). It doesn't matter if the
// device supports per-application provisioning or not.
EXPECT_CALL(*this, GetProvisioningResult()).WillRepeatedly(Return(true));
Initialize();
MediaDrmOriginId origin_id = GetOriginId();
test_browser_thread_bundle_.RunUntilIdle();
// Check that the preference does not contain |origin_id|.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
auto* list = dict->FindKey(kAvailableOriginIds);
EXPECT_FALSE(ContainsValue(list->GetList(),
CreateUnguessableTokenValue(origin_id.value())));
}
TEST_F(MediaDrmOriginIdManagerTest, ProvisioningFail) {
// Provisioning fails, so GetOriginId() returns an empty origin ID.
EXPECT_CALL(*this, GetProvisioningResult()).WillOnce(testing::Return(false));
Initialize();
EXPECT_FALSE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// After failure the preference should contain |kExpireableToken| only if
// per-application provisioning is NOT supported.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
if (media::MediaDrmBridge::IsPerApplicationProvisioningSupported()) {
DVLOG(1) << "Per-application provisioning is supported.";
EXPECT_FALSE(dict->FindKey(kExpirableToken));
} else {
DVLOG(1) << "Per-application provisioning is NOT supported.";
EXPECT_TRUE(dict->FindKey(kExpirableToken));
}
}
TEST_F(MediaDrmOriginIdManagerTest, ProvisioningSuccessAfterFail) {
// Provisioning fails, so GetOriginId() returns an empty origin ID.
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
Initialize();
EXPECT_FALSE(GetOriginId());
EXPECT_TRUE(GetOriginId()); // Provisioning will succeed on the second call.
// Let pre-provisioning of other origin IDs finish.
test_browser_thread_bundle_.RunUntilIdle();
// After success the preference should not contain |kExpireableToken|.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kExpirableToken));
// As well, the list of available pre-provisioned origin IDs should be full.
auto* list = dict->FindKey(kAvailableOriginIds);
EXPECT_TRUE(list->is_list());
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
}
TEST_F(MediaDrmOriginIdManagerTest, ProvisioningAfterExpiration) {
// Provisioning fails, so GetOriginId() returns an empty origin ID.
DVLOG(1) << "Current time: " << base::Time::Now();
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
Initialize();
EXPECT_FALSE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| in the preference is empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Check that |kExpirableToken| is only set if per-application provisioning is
// not supported.
EXPECT_TRUE(media::MediaDrmBridge::IsPerApplicationProvisioningSupported() ||
dict->FindKey(kExpirableToken));
// Advance clock by |kExpirationDelta| (plus one minute) and attempt to
// pre-provision more origin Ids.
DVLOG(1) << "Advancing Time";
test_browser_thread_bundle_.FastForwardBy(kExpirationDelta);
test_browser_thread_bundle_.FastForwardBy(base::TimeDelta::FromMinutes(1));
DVLOG(1) << "Adjusted time: " << base::Time::Now();
PreProvision();
test_browser_thread_bundle_.RunUntilIdle();
// Look at the preference again.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
auto* list = dict->FindKey(kAvailableOriginIds);
if (media::MediaDrmBridge::IsPerApplicationProvisioningSupported()) {
// If per-application provisioning is supported, it's OK to attempt
// to pre-provision origin IDs any time.
DVLOG(1) << "Per-application provisioning is supported.";
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
EXPECT_FALSE(dict->FindKey(kExpirableToken));
} else {
// Per-application provisioning is not supported, so attempting to
// pre-provision origin IDs after |kExpirationDelta| should not do anything.
// As well, |kExpirableToken| should be removed.
DVLOG(1) << "Per-application provisioning is NOT supported.";
EXPECT_FALSE(list);
EXPECT_FALSE(dict->FindKey(kExpirableToken));
}
}
TEST_F(MediaDrmOriginIdManagerTest, Incognito) {
// No MediaDrmOriginIdManager should be created for an incognito profile.
Initialize();
auto* incognito_profile = profile_->GetOffTheRecordProfile();
EXPECT_FALSE(
MediaDrmOriginIdManagerFactory::GetForProfile(incognito_profile));
}
TEST_F(MediaDrmOriginIdManagerTest, NetworkChange) {
// Try to pre-provision a bunch of origin IDs. Provisioning will fail, so
// there will not be a bunch of origin IDs created. However, it should be
// watching for a network change.
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
EXPECT_CALL(*this, GetProvisioningResult())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
Initialize();
EXPECT_FALSE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| in the preference is empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Provisioning will now "succeed", so trigger a network change to
// unconnected.
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE);
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| is still empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Now trigger a network change to connected.
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
test_browser_thread_bundle_.RunUntilIdle();
// Pre-provisioning should have run and filled up the list.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
auto* list = dict->FindKey(kAvailableOriginIds);
EXPECT_EQ(list->GetList().size(), kExpectedPreferenceListSize);
}
TEST_F(MediaDrmOriginIdManagerTest, NetworkChangeFails) {
// Try to pre-provision a bunch of origin IDs. Provisioning will fail the
// first time, so there will not be a bunch of origin IDs created. However, it
// should be watching for a network change, and will try again on the next
// |kConnectionAttempts| connections to a network. GetProvisioningResult()
// should only be called once for the GetOriginId() call +
// |kConnectionAttempts| when a network connection is detected.
// TODO(crbug.com/917527): Currently the code returns an origin ID even if
// provisioning fails. Update this once it returns an empty origin ID when
// pre-provisioning fails.
EXPECT_CALL(*this, GetProvisioningResult())
.Times(kConnectionAttempts + 1)
.WillOnce(Return(false));
Initialize();
EXPECT_FALSE(GetOriginId());
test_browser_thread_bundle_.RunUntilIdle();
// Check that |kAvailableOriginIds| in the preference is empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds;
auto* dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
// Trigger multiple network connections (provisioning still fails). Call more
// than |kConnectionAttempts| to ensure that the network change is ignored
// after several failed attempts.
for (size_t i = 0; i < kConnectionAttempts + 3; ++i) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
test_browser_thread_bundle_.RunUntilIdle();
}
// Check that |kAvailableOriginIds| is still empty.
DVLOG(1) << "Checking preference " << kMediaDrmOriginIds << " again";
dict = GetDictionary(kMediaDrmOriginIds);
DVLOG(1) << DisplayPref(dict);
EXPECT_FALSE(dict->FindKey(kAvailableOriginIds));
}