chromium/chrome/browser/ash/child_accounts/parent_access_code/parent_access_service.cc

// Copyright 2019 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/child_accounts/parent_access_code/parent_access_service.h"

#include <string>
#include <utility>

#include "ash/public/cpp/child_accounts/parent_access_controller.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"

namespace ash {
namespace parent_access {

namespace {

// Returns true when the device owner is a child.
bool IsDeviceOwnedByChild() {
  AccountId owner_account_id =
      user_manager::UserManager::Get()->GetOwnerAccountId();
  if (owner_account_id.empty()) {
    LOG(ERROR) << "Device owner could not be determined - will skip parent "
                  "code validation";
    return false;
  }

  const user_manager::User* device_owner =
      user_manager::UserManager::Get()->FindUser(owner_account_id);

  // It looks like reading users from Local State might be failing sometimes.
  // Default to false if ownership is not known to avoid crash.
  // TODO(agawronska): Investigate if it can be improved. Defaulting to false
  // could sometimes lead to skipping parent code validation when child is the
  // device owner.
  if (!device_owner) {
    LOG(ERROR) << "Device owner could not be determined - will skip parent "
                  "code validation";
    return false;
  }
  return device_owner->IsChild();
}

// Returns true is any parent code config is available on the device.
bool IsParentCodeConfigAvailable() {
  const user_manager::UserList& users =
      user_manager::UserManager::Get()->GetUsers();
  user_manager::KnownUser known_user(g_browser_process->local_state());
  for (const user_manager::User* user : users) {
    if (known_user.FindPath(user->GetAccountId(),
                            prefs::kKnownUserParentAccessCodeConfig)) {
      return true;
    }
  }
  return false;
}

}  // namespace

// static
void ParentAccessService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  registry->RegisterDictionaryPref(prefs::kParentAccessCodeConfig);
}

// static
ParentAccessService& ParentAccessService::Get() {
  static base::NoDestructor<ParentAccessService> instance;
  return *instance;
}

// static
bool ParentAccessService::IsApprovalRequired(SupervisedAction action) {
  switch (action) {
    case SupervisedAction::kUpdateClock:
    case SupervisedAction::kUpdateTimezone:
      if (user_manager::UserManager::Get()->IsUserLoggedIn())
        return user_manager::UserManager::Get()->GetActiveUser()->IsChild();
      return IsDeviceOwnedByChild();
    case SupervisedAction::kAddUser:
      if (!features::IsParentAccessCodeForOnlineLoginEnabled())
        return false;
      return IsDeviceOwnedByChild();
    case SupervisedAction::kReauth:
      if (!features::IsParentAccessCodeForOnlineLoginEnabled())
        return false;
      if (!IsParentCodeConfigAvailable())
        return false;
      return IsDeviceOwnedByChild();
    case SupervisedAction::kUnlockTimeLimits:
      DCHECK(user_manager::UserManager::Get()->IsUserLoggedIn());
      return true;
  }
}

ParentAccessService::ParentAccessService() = default;

ParentAccessService::~ParentAccessService() = default;

ParentCodeValidationResult ParentAccessService::ValidateParentAccessCode(
    const AccountId& account_id,
    const std::string& access_code,
    base::Time validation_time) {
  ParentCodeValidationResult result = ParentCodeValidationResult::kInvalid;

  if (config_source_.config_map().empty() ||
      (account_id.is_valid() &&
       !base::Contains(config_source_.config_map(), account_id))) {
    result = ParentCodeValidationResult::kNoConfig;
    NotifyObservers(result, account_id);
    return result;
  }

  for (const auto& map_entry : config_source_.config_map()) {
    if (!account_id.is_valid() || account_id == map_entry.first) {
      for (const auto& validator : map_entry.second) {
        if (validator->Validate(access_code, validation_time)) {
          result = ParentCodeValidationResult::kValid;
          NotifyObservers(result, account_id);
          return result;
        }
      }
    }
  }

  NotifyObservers(result, account_id);
  return result;
}

void ParentAccessService::LoadConfigForUser(const user_manager::User* user) {
  config_source_.LoadConfigForUser(user);
}

void ParentAccessService::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void ParentAccessService::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void ParentAccessService::NotifyObservers(
    ParentCodeValidationResult validation_result,
    const AccountId& account_id) {
  for (auto& observer : observers_)
    observer.OnAccessCodeValidation(validation_result, account_id);
}

}  // namespace parent_access
}  // namespace ash