chromium/chrome/browser/ash/printing/enterprise/enterprise_printers_provider.cc

// Copyright 2018 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/ash/printing/enterprise/enterprise_printers_provider.h"

#include <iterator>
#include <unordered_map>
#include <utility>
#include <vector>

#include "base/hash/md5.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "chrome/browser/ash/printing/enterprise/bulk_printers_calculator.h"
#include "chrome/browser/ash/printing/enterprise/bulk_printers_calculator_factory.h"
#include "chrome/browser/ash/printing/enterprise/calculators_policies_binder.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/printer_translator.h"
#include "components/device_event_log/device_event_log.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/policy_constants.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"

namespace ash {

namespace {

std::vector<std::string> ConvertToVector(const base::Value::List& list) {
  std::vector<std::string> string_list;
  for (const base::Value& value : list) {
    if (value.is_string()) {
      string_list.push_back(value.GetString());
    }
  }
  return string_list;
}

class EnterprisePrintersProviderImpl : public EnterprisePrintersProvider,
                                       public BulkPrintersCalculator::Observer {
 public:
  EnterprisePrintersProviderImpl(CrosSettings* settings, Profile* profile)
      : profile_(profile) {
    // initialization of pref_change_registrar
    pref_change_registrar_.Init(profile->GetPrefs());

    auto* factory = BulkPrintersCalculatorFactory::Get();
    if (!factory) {
      DVLOG(1) << "Factory is null.  Policies are unbound.  This is only "
                  "expected in unit tests";
      return;
    }

    // Get instance of BulkPrintersCalculator for device policies.
    device_printers_ = factory->GetForDevice();
    if (device_printers_) {
      devices_binder_ =
          CalculatorsPoliciesBinder::DeviceBinder(settings, device_printers_);
      device_printers_->AddObserver(this);
      RecalculateCompleteFlagForDevicePrinters();
    }

    // Calculate account_id_ and get instance of BulkPrintersCalculator for user
    // policies.
    const user_manager::User* user =
        ProfileHelper::Get()->GetUserByProfile(profile);
    if (user) {
      account_id_ = user->GetAccountId();
      user_printers_ = factory->GetForAccountId(account_id_);
      // Binds instances of BulkPrintersCalculator to policies.
      profile_binder_ = CalculatorsPoliciesBinder::UserBinder(
          profile->GetPrefs(), user_printers_);
      user_printers_->AddObserver(this);
      RecalculateCompleteFlagForUserPrinters();
    }

    // Binds policy with recommended printers (deprecated). This method calls
    // indirectly RecalculateCurrentPrintersList() that prepares the first
    // version of final list of printers.
    BindPref(prefs::kRecommendedPrinters,
             &EnterprisePrintersProviderImpl::UpdateUserRecommendedPrinters);
  }

  EnterprisePrintersProviderImpl(const EnterprisePrintersProviderImpl&) =
      delete;
  EnterprisePrintersProviderImpl& operator=(
      const EnterprisePrintersProviderImpl&) = delete;

  ~EnterprisePrintersProviderImpl() override {
    if (device_printers_)
      device_printers_->RemoveObserver(this);
    if (user_printers_) {
      user_printers_->RemoveObserver(this);
    }
  }

  void AddObserver(EnterprisePrintersProvider::Observer* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    observers_.AddObserver(observer);
    observer->OnPrintersChanged(complete_, printers_);
  }

  void RemoveObserver(EnterprisePrintersProvider::Observer* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    observers_.RemoveObserver(observer);
  }

  // BulkPrintersCalculator::Observer implementation
  void OnPrintersChanged(const BulkPrintersCalculator* sender) override {
    if (device_printers_ && sender == device_printers_.get()) {
      RecalculateCompleteFlagForDevicePrinters();
    } else if (user_printers_ && sender == user_printers_.get()) {
      RecalculateCompleteFlagForUserPrinters();
    }
    RecalculateCurrentPrintersList();
  }

 private:
  // This method process value from the deprecated policy with recommended
  // printers. It is called when value of the policy changes.
  void UpdateUserRecommendedPrinters() {
    recommended_printers_.clear();
    std::vector<std::string> data = FromPrefs(prefs::kRecommendedPrinters);
    for (const auto& printer_json : data) {
      std::optional<base::Value> printer_value = base::JSONReader::Read(
          printer_json, base::JSON_ALLOW_TRAILING_COMMAS);
      if (!printer_value.has_value() || !printer_value.value().is_dict()) {
        LOG(WARNING) << "Ignoring invalid printer.  Invalid JSON object: "
                     << printer_json;
        continue;
      }

      // Policy printers don't have id's but the ids only need to be locally
      // unique so we'll hash the record.  This will not collide with the
      // UUIDs generated for user entries.
      std::string id = base::MD5String(printer_json);
      base::Value::Dict& printer_dictionary = printer_value.value().GetDict();
      printer_dictionary.Set(chromeos::kPrinterId, id);

      auto new_printer =
          chromeos::RecommendedPrinterToPrinter(printer_dictionary);
      if (!new_printer) {
        LOG(WARNING) << "Recommended printer is malformed.";
        continue;
      }

      bool inserted = recommended_printers_.insert({id, *new_printer}).second;
      if (!inserted) {
        // Printer is already in the list.
        LOG(WARNING) << "Duplicate printer ignored: " << id;
        continue;
      }
    }
    RecalculateCurrentPrintersList();
  }

  // These three methods calculate resultant list of printers and complete flag.

  void RecalculateCompleteFlagForUserPrinters() {
    if (!user_printers_) {
      user_printers_is_complete_ = true;
      return;
    }

    user_printers_is_complete_ =
        user_printers_->IsComplete() &&
        (user_printers_->IsDataPolicySet() ||
         !PolicyWithDataIsSet(policy::key::kPrintersBulkConfiguration));
  }

  void RecalculateCompleteFlagForDevicePrinters() {
    if (!device_printers_) {
      device_printers_is_complete_ = true;
      return;
    }

    device_printers_is_complete_ =
        device_printers_->IsComplete() &&
        (device_printers_->IsDataPolicySet() ||
         !PolicyWithDataIsSet(policy::key::kDevicePrinters));
  }

  void RecalculateCurrentPrintersList() {
    complete_ = true;

    // Enterprise printers from user policy, device policy, as well as printers
    // from the legacy `Printers` policy.
    std::unordered_map<std::string, chromeos::Printer> all_printers =
        recommended_printers_;

    if (device_printers_) {
      complete_ = complete_ && device_printers_is_complete_;
      std::unordered_map<std::string, chromeos::Printer> printers =
          device_printers_->GetPrinters();
      PRINTER_LOG(DEBUG)
          << "EnterprisePrintersProvider::RecalculateCurrentPrintersList()"
          << "-device-printers: complete=" << device_printers_is_complete_
          << " count=" << printers.size();

      all_printers.merge(std::move(printers));
    }
    if (user_printers_) {
      complete_ = complete_ && user_printers_is_complete_;
      std::unordered_map<std::string, chromeos::Printer> printers =
          user_printers_->GetPrinters();
      PRINTER_LOG(DEBUG)
          << "EnterprisePrintersProvider::RecalculateCurrentPrintersList()"
          << "-user-printers: complete=" << user_printers_is_complete_
          << " count=" << printers.size();
      all_printers.merge(std::move(printers));
    }

    // Update `printers_` with the recalculated result.
    printers_.clear();
    printers_.reserve(all_printers.size());
    base::ranges::transform(all_printers, std::back_inserter(printers_),
                            [](const auto& p) { return p.second; });

    for (auto& observer : observers_) {
      observer.OnPrintersChanged(complete_, printers_);
    }
  }

  typedef void (EnterprisePrintersProviderImpl::*SimpleMethod)();

  // Binds given user policy to given method and calls this method once.
  void BindPref(const char* policy_name, SimpleMethod method_to_call) {
    pref_change_registrar_.Add(
        policy_name,
        base::BindRepeating(method_to_call, base::Unretained(this)));
    (this->*method_to_call)();
  }

  // Extracts the list of strings named |policy_name| from user policies.
  std::vector<std::string> FromPrefs(const std::string& policy_name) {
    return ConvertToVector(profile_->GetPrefs()->GetList(policy_name));
  }

  // Checks if given policy is set and if it is a dictionary
  bool PolicyWithDataIsSet(const char* policy_name) {
    policy::ProfilePolicyConnector* policy_connector =
        profile_->GetProfilePolicyConnector();
    if (!policy_connector) {
      // something is wrong
      return false;
    }
    const policy::PolicyNamespace policy_namespace =
        policy::PolicyNamespace(policy::PolicyDomain::POLICY_DOMAIN_CHROME, "");
    const policy::PolicyMap& policy_map =
        policy_connector->policy_service()->GetPolicies(policy_namespace);
    const base::Value* value =
        policy_map.GetValue(policy_name, base::Value::Type::DICT);
    return value != nullptr;
  }

  // current partial results
  std::unordered_map<std::string, chromeos::Printer> recommended_printers_;
  bool device_printers_is_complete_ = true;
  bool user_printers_is_complete_ = true;

  // current final results
  bool complete_ = false;
  std::vector<chromeos::Printer> printers_;

  // Calculators for bulk printers from device and user policies. Unowned.
  base::WeakPtr<BulkPrintersCalculator> device_printers_;
  base::WeakPtr<BulkPrintersCalculator> user_printers_;

  // Policies binder (bridge between policies and calculators). Owned.
  std::unique_ptr<CalculatorsPoliciesBinder> devices_binder_;
  std::unique_ptr<CalculatorsPoliciesBinder> profile_binder_;

  // Profile (user) settings.
  raw_ptr<Profile> profile_;
  AccountId account_id_;
  PrefChangeRegistrar pref_change_registrar_;

  base::ObserverList<EnterprisePrintersProvider::Observer>::Unchecked
      observers_;
  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace

// static
void EnterprisePrintersProvider::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterListPref(prefs::kRecommendedPrinters);
  CalculatorsPoliciesBinder::RegisterProfilePrefs(registry);
}

// static
std::unique_ptr<EnterprisePrintersProvider> EnterprisePrintersProvider::Create(
    CrosSettings* settings,
    Profile* profile) {
  return std::make_unique<EnterprisePrintersProviderImpl>(settings, profile);
}

}  // namespace ash