chromium/chrome/browser/ash/system/timezone_resolver_manager.cc

// Copyright 2016 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/system/timezone_resolver_manager.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/constants/geolocation_access_level.h"
#include "ash/shell.h"
#include "ash/system/privacy_hub/privacy_hub_controller.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "chrome/browser/ash/net/delay_network_call.h"
#include "chrome/browser/ash/preferences/preferences.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system/input_device_settings.h"
#include "chrome/browser/ash/system/timezone_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/geolocation/simple_geolocation_provider.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"

namespace ash {
namespace system {

namespace {

// This is the result of several methods calculating configured
// time zone resolve processes.
enum ServiceConfiguration {
  UNSPECIFIED = 0,   // Try another configuration source.
  SHOULD_START = 1,  // This source requires service Start.
  SHOULD_STOP = 2,   // This source requires service Stop.
};

// Starts or stops TimezoneResolver if required by
// SystemTimezoneAutomaticDetectionPolicy.
// Returns SHOULD_* if timezone resolver status is controlled by this policy.
ServiceConfiguration GetServiceConfigurationFromAutomaticDetectionPolicy() {
  PrefService* local_state = g_browser_process->local_state();
  const bool is_managed = local_state->IsManagedPreference(
      ::prefs::kSystemTimezoneAutomaticDetectionPolicy);
  if (!is_managed)
    return UNSPECIFIED;

  int policy_value =
      local_state->GetInteger(::prefs::kSystemTimezoneAutomaticDetectionPolicy);

  switch (policy_value) {
    case enterprise_management::SystemTimezoneProto::USERS_DECIDE:
      return UNSPECIFIED;
    case enterprise_management::SystemTimezoneProto::DISABLED:
      return SHOULD_STOP;
    case enterprise_management::SystemTimezoneProto::IP_ONLY:
      return SHOULD_START;
    case enterprise_management::SystemTimezoneProto::SEND_WIFI_ACCESS_POINTS:
      return SHOULD_START;
    case enterprise_management::SystemTimezoneProto::SEND_ALL_LOCATION_INFO:
      return SHOULD_START;
  }
  // Default for unknown policy value.
  NOTREACHED_IN_MIGRATION() << "Unrecognized policy value: " << policy_value;
  return SHOULD_STOP;
}

// Stops TimezoneResolver if SystemTimezonePolicy is applied.
// Returns SHOULD_* if timezone resolver status is controlled by this policy.
ServiceConfiguration GetServiceConfigurationFromSystemTimezonePolicy() {
  if (!HasSystemTimezonePolicy())
    return UNSPECIFIED;

  return SHOULD_STOP;
}

// Starts or stops TimezoneResolver if required by policy.
// Returns true if timezone resolver status is controlled by policy.
ServiceConfiguration GetServiceConfigurationFromPolicy() {
  ServiceConfiguration result =
      GetServiceConfigurationFromSystemTimezonePolicy();

  if (result != UNSPECIFIED)
    return result;

  result = GetServiceConfigurationFromAutomaticDetectionPolicy();
  return result;
}

// Returns service configuration for the user.
ServiceConfiguration GetServiceConfigurationFromUserPrefs(
    const PrefService* user_prefs) {
  return TimeZoneResolverManager::TimeZoneResolveMethodFromInt(
             user_prefs->GetInteger(
                 ::prefs::kResolveTimezoneByGeolocationMethod)) ==
                 TimeZoneResolverManager::TimeZoneResolveMethod::DISABLED
             ? SHOULD_STOP
             : SHOULD_START;
}

// Returns service configuration for the signin screen.
ServiceConfiguration GetServiceConfigurationForSigninScreen() {
  using AccessLevel = GeolocationAccessLevel;

  const AccessLevel device_geolocation_permission =
      static_cast<AccessLevel>(g_browser_process->local_state()->GetInteger(
          prefs::kDeviceGeolocationAllowed));
  if (device_geolocation_permission == AccessLevel::kDisallowed) {
    return SHOULD_STOP;
  }

  const PrefService::Preference* device_pref =
      g_browser_process->local_state()->FindPreference(
          ::prefs::kResolveDeviceTimezoneByGeolocationMethod);
  if (!device_pref || device_pref->IsDefaultValue()) {
    // CfM devices default to static timezone.
    bool keyboard_driven_oobe =
        system::InputDeviceSettings::Get()->ForceKeyboardDrivenUINavigation();
    return keyboard_driven_oobe ? SHOULD_STOP : SHOULD_START;
  }

  // Do not start resolver if we are inside active user session.
  // If user preferences permit, it will be started on preferences
  // initialization.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kLoginUser))
    return SHOULD_STOP;

  return TimeZoneResolverManager::TimeZoneResolveMethodFromInt(
             device_pref->GetValue()->GetInt()) ==
                 TimeZoneResolverManager::TimeZoneResolveMethod::DISABLED
             ? SHOULD_STOP
             : SHOULD_START;
}

}  // anonymous namespace.

TimeZoneResolverManager::TimeZoneResolverManager(
    SimpleGeolocationProvider* geolocation_provider,
    session_manager::SessionManager* session_manager)
    : geolocation_provider_(geolocation_provider) {
  switch (g_browser_process->local_state()->GetInitializationStatus()) {
    case PrefService::INITIALIZATION_STATUS_SUCCESS:
    case PrefService::INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE:
      local_state_initialized_ = true;
      break;
    default:
      local_state_initialized_ = false;
  }
  g_browser_process->local_state()->AddPrefInitObserver(
      base::BindOnce(&TimeZoneResolverManager::OnLocalStateInitialized,
                     weak_factory_.GetWeakPtr()));

  local_state_pref_change_registrar_.Init(g_browser_process->local_state());
  local_state_pref_change_registrar_.Add(
      ::prefs::kSystemTimezoneAutomaticDetectionPolicy,
      base::BindRepeating(&TimeZoneResolverManager::UpdateTimezoneResolver,
                          base::Unretained(this)));

  geolocation_provider_->AddObserver(this);
  session_observation_.Observe(session_manager);
}

TimeZoneResolverManager::~TimeZoneResolverManager() {
  geolocation_provider_->RemoveObserver(this);
  geolocation_provider_ = nullptr;
}

void TimeZoneResolverManager::SetPrimaryUserPrefs(PrefService* pref_service) {
  primary_user_prefs_ = pref_service;
}

bool TimeZoneResolverManager::ShouldSendWiFiGeolocationData() const {
  // Managed user case, check cloud policies for automatic time zone.
  if (IsTimeZoneResolutionPolicyControlled()) {
    switch (GetEffectiveAutomaticTimezoneManagementSetting()) {
      case enterprise_management::SystemTimezoneProto::SEND_WIFI_ACCESS_POINTS:
        return true;
      case enterprise_management::SystemTimezoneProto::SEND_ALL_LOCATION_INFO:
        return true;
      case enterprise_management::SystemTimezoneProto::USERS_DECIDE:
        // Same as regular user flow, continue processing below.
        break;
      default:
        return false;
    }
  }

  // Regular user case (also USERS_DECIDE case for managed).
  // primary_user_prefs_ indicates if the user has signed in or not.
  // Precise location is disabled on log-in screen.
  if (!primary_user_prefs_) {
    return false;
  }

  // User is logged in at this point.
  // Check that user location permission is granted for system services.
  if (static_cast<GeolocationAccessLevel>(primary_user_prefs_->GetInteger(
          ash::prefs::kUserGeolocationAccessLevel)) ==
      GeolocationAccessLevel::kDisallowed) {
    return false;
  }

  // Automatic time zone setting is a user configurable option, applying
  // the primary user's choice to the entire session.
  switch (GetEffectiveUserTimeZoneResolveMethod(primary_user_prefs_,
                                                /*check_policy=*/false)) {
    case TimeZoneResolveMethod::SEND_WIFI_ACCESS_POINTS:
      return true;
    case TimeZoneResolveMethod::SEND_ALL_LOCATION_INFO:
      return true;
    default:
      return false;
  }
}

bool TimeZoneResolverManager::ShouldSendCellularGeolocationData() const {
  // Managed user case, check cloud policies for automatic time zone.
  if (IsTimeZoneResolutionPolicyControlled()) {
    switch (GetEffectiveAutomaticTimezoneManagementSetting()) {
      case enterprise_management::SystemTimezoneProto::SEND_ALL_LOCATION_INFO:
        return true;
      case enterprise_management::SystemTimezoneProto::USERS_DECIDE:
        // Same as regular user flow, continue processing below.
        break;
      default:
        return false;
    }
  }

  // Regular user case (also USERS_DECIDE case for managed).
  // primary_user_prefs_ indicates if the user has signed in or not.
  // Precise location is disabled on log-in screen.
  if (!primary_user_prefs_) {
    return false;
  }

  // User is logged in at this point.
  // Check that user location permission is granted for system services.
  if (static_cast<GeolocationAccessLevel>(primary_user_prefs_->GetInteger(
          ash::prefs::kUserGeolocationAccessLevel)) ==
      GeolocationAccessLevel::kDisallowed) {
    return false;
  }

  // Automatic time zone setting is a user configurable option, applying
  // the primary user's choice to the entire session.
  return GetEffectiveUserTimeZoneResolveMethod(primary_user_prefs_,
                                               /*check_policy=*/false) ==
         TimeZoneResolveMethod::SEND_ALL_LOCATION_INFO;
}

// static
int TimeZoneResolverManager::GetEffectiveAutomaticTimezoneManagementSetting() {
  // Regular users choose automatic time zone method themselves.
  if (!IsTimeZoneResolutionPolicyControlled()) {
    return enterprise_management::SystemTimezoneProto::USERS_DECIDE;
  }

  // Static time zone policy overrides automatic.
  if (HasSystemTimezonePolicy()) {
    return enterprise_management::SystemTimezoneProto::DISABLED;
  }

  int policy_value = g_browser_process->local_state()->GetInteger(
      ::prefs::kSystemTimezoneAutomaticDetectionPolicy);
  DCHECK(policy_value <= enterprise_management::SystemTimezoneProto::
                             AutomaticTimezoneDetectionType_MAX);

  return policy_value;
}

void TimeZoneResolverManager::OnUserProfileLoaded(const AccountId& account_id) {
  Profile* profile = ProfileHelper::Get()->GetProfileByAccountId(account_id);
  system::UpdateSystemTimezone(profile);

  auto* user_manager = user_manager::UserManager::Get();
  const auto* user = user_manager->FindUser(account_id);
  if (!user) {
    return;
  }

  // In Multi-Profile mode only primary user settings are in effect.
  if (user != user_manager->GetPrimaryUser()) {
    return;
  }

  if (!user_manager->IsUserLoggedIn()) {
    return;
  }

  // Timezone auto refresh is disabled for Guest and OffTheRecord
  // users, but enabled for Kiosk mode.
  if (user_manager->IsLoggedInAsGuest() || profile->IsOffTheRecord()) {
    GetResolver()->Stop();
    return;
  }
  UpdateTimezoneResolver();
}

void TimeZoneResolverManager::UpdateTimezoneResolver() {
  initialized_ = true;
  TimeZoneResolver* resolver = GetResolver();
  // Local state becomes initialized when policy data is loaded,
  // and we need policies to decide whether resolver can be started.
  if (!local_state_initialized_) {
    resolver->Stop();
    return;
  }
  if (TimeZoneResolverShouldBeRunning())
    resolver->Start();
  else
    resolver->Stop();

  // Observers must be notified whenever UpdateTimezoneResolver() is called.
  // This allows observers to listen for all relevant prefs updates.
  for (Observer& observer : observers_)
    observer.OnTimeZoneResolverUpdated();
}

void TimeZoneResolverManager::OnGeolocationPermissionChanged(bool enabled) {
  // New permission state will be retrieved from `geolocation_provider_`.
  UpdateTimezoneResolver();
}

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

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

bool TimeZoneResolverManager::ShouldApplyResolvedTimezone() {
  return TimeZoneResolverShouldBeRunning();
}

bool TimeZoneResolverManager::TimeZoneResolverShouldBeRunning() {
  // System geolocation permission is required for automatic timezone
  // resolution.
  if (!geolocation_provider_->IsGeolocationUsageAllowedForSystem()) {
    return false;
  }

  // Once the permission is granted, it's all up to the time zone
  // configuration data.
  return TimeZoneResolverAllowedByTimeZoneConfigData();
}

bool TimeZoneResolverManager::TimeZoneResolverAllowedByTimeZoneConfigData() {
  ServiceConfiguration result = GetServiceConfigurationFromPolicy();

  if (result == UNSPECIFIED) {
    if (primary_user_prefs_) {
      result = GetServiceConfigurationFromUserPrefs(primary_user_prefs_);
    } else {
      // We are on a signin page.
      result = GetServiceConfigurationForSigninScreen();
    }
  }
  return result == SHOULD_START;
}

ash::TimeZoneResolver* TimeZoneResolverManager::GetResolver() {
  if (!timezone_resolver_.get()) {
    timezone_resolver_ = std::make_unique<ash::TimeZoneResolver>(
        this, geolocation_provider_,
        g_browser_process->shared_url_loader_factory(),
        base::BindRepeating(&ash::system::ApplyTimeZone),
        base::BindRepeating(&ash::DelayNetworkCall),
        g_browser_process->local_state());
  }
  return timezone_resolver_.get();
}

void TimeZoneResolverManager::OnLocalStateInitialized(bool initialized) {
  local_state_initialized_ = initialized;
  if (initialized_)
    UpdateTimezoneResolver();
}

// static
TimeZoneResolverManager::TimeZoneResolveMethod
TimeZoneResolverManager::TimeZoneResolveMethodFromInt(int value) {
  if (value < 0 ||
      value >= static_cast<int>(TimeZoneResolveMethod::METHODS_NUMBER)) {
    return TimeZoneResolveMethod::DISABLED;
  }

  const TimeZoneResolveMethod method =
      static_cast<TimeZoneResolveMethod>(value);

  if (FineGrainedTimeZoneDetectionEnabled())
    return method;

  if (method == TimeZoneResolveMethod::DISABLED)
    return TimeZoneResolveMethod::DISABLED;

  return TimeZoneResolveMethod::IP_ONLY;
}

// static
TimeZoneResolverManager::TimeZoneResolveMethod
TimeZoneResolverManager::GetEffectiveUserTimeZoneResolveMethod(
    const PrefService* user_prefs,
    bool check_policy) {
  if (check_policy) {
    int policy_value = GetEffectiveAutomaticTimezoneManagementSetting();
    switch (policy_value) {
      case enterprise_management::SystemTimezoneProto::USERS_DECIDE:
        // Follow user preference.
        break;
      case enterprise_management::SystemTimezoneProto::DISABLED:
        return TimeZoneResolveMethod::DISABLED;
      case enterprise_management::SystemTimezoneProto::IP_ONLY:
        return TimeZoneResolveMethod::IP_ONLY;
      case enterprise_management::SystemTimezoneProto::SEND_WIFI_ACCESS_POINTS:
        return TimeZoneResolveMethod::SEND_WIFI_ACCESS_POINTS;
      case enterprise_management::SystemTimezoneProto::SEND_ALL_LOCATION_INFO:
        return TimeZoneResolveMethod::SEND_ALL_LOCATION_INFO;
      default:
        NOTREACHED_IN_MIGRATION();
        return TimeZoneResolveMethod::DISABLED;
    }
  }
  if (user_prefs->GetBoolean(
          ::prefs::kResolveTimezoneByGeolocationMigratedToMethod)) {
    return TimeZoneResolveMethodFromInt(
        user_prefs->GetInteger(::prefs::kResolveTimezoneByGeolocationMethod));
  }
  return TimeZoneResolveMethod::IP_ONLY;
}

// static
bool TimeZoneResolverManager::IsTimeZoneResolutionPolicyControlled() {
  return GetServiceConfigurationFromPolicy() != UNSPECIFIED;
}

// static
bool TimeZoneResolverManager::IfServiceShouldBeRunningForSigninScreen() {
  return GetServiceConfigurationForSigninScreen() == SHOULD_START;
}

}  // namespace system
}  // namespace ash