chromium/chrome/browser/win/parental_controls.cc

// 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();
}