// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/win/parental_controls.h"
#include <windows.h>
#include <combaseapi.h>
#include <winerror.h>
#include <wpcapi.h>
#include <wrl/client.h>
#include <string>
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/strings/strcat_win.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "base/win/windows_types.h"
namespace {
static bool g_has_called_initialize_win_parental_controls_ = false;
// This singleton allows us to attempt to calculate the Platform Parental
// Controls enabled value on a worker thread before the UI thread needs the
// value. If the UI thread finishes sooner than we expect, that's no worse than
// today where we block.
class WinParentalControlsValue {
public:
static WinParentalControlsValue* GetInstance() {
return base::Singleton<WinParentalControlsValue>::get();
}
const WinParentalControls& parental_controls() const {
return parental_controls_;
}
private:
friend struct base::DefaultSingletonTraits<WinParentalControlsValue>;
WinParentalControlsValue() : parental_controls_(GetParentalControls()) {}
~WinParentalControlsValue() = default;
WinParentalControlsValue(const WinParentalControlsValue&) = delete;
WinParentalControlsValue& operator=(const WinParentalControlsValue&) = delete;
// Returns the Windows Parental control enablements. This feature is available
// on Windows 7 and beyond. This function should be called on a COM
// Initialized thread and is potentially blocking.
static WinParentalControls GetParentalControlsFromApi() {
// This call may block and be called from the UI thread, which is
// unfortunate, but we want to at least make sure that we've attempted to
// call InitializeWinParentalControls() in an attempt to load it early so
// that we don't need to block.
//
// Note that this CHECK replaced a previous base::ScopedBlockingCall, which
// was incorrect because there were no guarantees that
// InitializeWinParentalControls() would finish executing asynchronously
// before the value was needed. See https://crbug.com/1411815#c7.
if (!g_has_called_initialize_win_parental_controls_) {
// This uses CHECK_IS_TEST() to skip verifying that
// InitializeWinParentalControls() got called in tests because updating
// all test fixtures to call it seemed daunting.
CHECK_IS_TEST();
}
Microsoft::WRL::ComPtr<IWindowsParentalControlsCore> parent_controls;
HRESULT hr = ::CoCreateInstance(__uuidof(WindowsParentalControls), nullptr,
CLSCTX_ALL, IID_PPV_ARGS(&parent_controls));
if (FAILED(hr))
return WinParentalControls();
Microsoft::WRL::ComPtr<IWPCSettings> settings;
hr = parent_controls->GetUserSettings(nullptr, &settings);
if (FAILED(hr))
return WinParentalControls();
DWORD restrictions = 0;
settings->GetRestrictions(&restrictions);
WinParentalControls controls = {
restrictions != WPCFLAG_NO_RESTRICTION /* any_restrictions */,
(restrictions & WPCFLAG_LOGGING_REQUIRED) == WPCFLAG_LOGGING_REQUIRED
/* logging_required */,
(restrictions & WPCFLAG_WEB_FILTERED) == WPCFLAG_WEB_FILTERED
/* web_filter */
};
return controls;
}
// Update |controls| with parental controls found to be active by reading
// parental controls configuration from the registry. May be necessary on
// Win10 where the APIs are not fully supported and may not always accurately
// report such state.
//
// TODO([email protected]): Detect |logging_required| configuration,
// rather than just web filtering.
static void UpdateParentalControlsFromRegistry(
WinParentalControls* controls) {
DCHECK(controls);
std::wstring user_sid;
if (!base::win::GetUserSidString(&user_sid))
return;
std::wstring web_filter_key_path =
base::StrCat({L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental "
L"Controls\\Users\\",
user_sid, L"\\Web"});
base::win::RegKey web_filter_key(
HKEY_LOCAL_MACHINE, web_filter_key_path.c_str(), KEY_QUERY_VALUE);
if (!web_filter_key.Valid())
return;
// Web filtering is in use if the key contains any non-zero "Filter On"
// value.
DWORD filter_on_value;
if (web_filter_key.ReadValueDW(L"Filter On", &filter_on_value) ==
ERROR_SUCCESS &&
filter_on_value) {
controls->any_restrictions = true;
controls->web_filter = true;
}
}
static WinParentalControls GetParentalControls() {
WinParentalControls controls = GetParentalControlsFromApi();
// Parental controls APIs are not fully supported in Win10 and beyond, so
// check registry properties for restictions.
UpdateParentalControlsFromRegistry(&controls);
return controls;
}
const WinParentalControls parental_controls_;
};
} // namespace
void InitializeWinParentalControls() {
g_has_called_initialize_win_parental_controls_ = true;
base::ThreadPool::CreateCOMSTATaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})
->PostTask(FROM_HERE, base::BindOnce(base::IgnoreResult(
&WinParentalControlsValue::GetInstance)));
}
const WinParentalControls& GetWinParentalControls() {
return WinParentalControlsValue::GetInstance()->parental_controls();
}