// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/policy/browser_policy_connector.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/path_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/cloud_policy_provider.h"
#include "chrome/browser/policy/cloud_policy_provider_impl.h"
#include "chrome/browser/policy/cloud_policy_subsystem.h"
#include "chrome/browser/policy/configuration_policy_provider.h"
#include "chrome/browser/policy/policy_service_impl.h"
#include "chrome/browser/policy/user_policy_cache.h"
#include "chrome/browser/policy/user_policy_token_cache.h"
#include "chrome/browser/signin/token_service.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "grit/generated_resources.h"
#include "policy/policy_constants.h"

#if defined(OS_WIN)
#include "chrome/browser/policy/configuration_policy_provider_win.h"
#elif defined(OS_MACOSX)
#include "chrome/browser/policy/configuration_policy_provider_mac.h"
#elif defined(OS_POSIX)
#include "chrome/browser/policy/config_dir_policy_provider.h"
#endif

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/system/statistics_provider.h"
#include "chrome/browser/policy/app_pack_updater.h"
#include "chrome/browser/policy/device_policy_cache.h"
#include "chrome/browser/policy/network_configuration_updater.h"
#endif

using content::BrowserThread;

namespace policy {

namespace {

// Subdirectory in the user's profile for storing user policies.
const FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Device Management");
// File in the above directory for stroing user policy dmtokens.
const FilePath::CharType kTokenCacheFile[] = FILE_PATH_LITERAL("Token");
// File in the above directory for storing user policy data.
const FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("Policy");

// The following constants define delays applied before the initial policy fetch
// on startup. (So that displaying Chrome's GUI does not get delayed.)
// Delay in milliseconds from startup.
const int64 kServiceInitializationStartupDelay = 5000;

#if defined(OS_CHROMEOS)
// MachineInfo key names.
const char kMachineInfoSystemHwqual[] = "hardware_class";

// These are the machine serial number keys that we check in order until we
// find a non-empty serial number. The VPD spec says the serial number should be
// in the "serial_number" key for v2+ VPDs. However, we cannot check this first,
// since we'd get the "serial_number" value from the SMBIOS (yes, there's a name
// clash here!), which is different from the serial number we want and not
// actually per-device. So, we check the legacy keys first. If we find a
// serial number for these, we use it, otherwise we must be on a newer device
// that provides the correct data in "serial_number".
const char* kMachineInfoSerialNumberKeys[] = {
  "sn",            // ZGB
  "Product_S/N",   // Alex
  "Product_SN",    // Mario
  "serial_number"  // VPD v2+ devices
};
#endif

}  // namespace

BrowserPolicyConnector::BrowserPolicyConnector()
    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {}

BrowserPolicyConnector::~BrowserPolicyConnector() {
  // Shutdown device cloud policy.
#if defined(OS_CHROMEOS)
  if (device_cloud_policy_subsystem_.get())
    device_cloud_policy_subsystem_->Shutdown();
  // The AppPackUpdater may be observing the |device_cloud_policy_subsystem_|.
  // Delete it first.
  app_pack_updater_.reset();
  device_cloud_policy_subsystem_.reset();
  device_data_store_.reset();
#endif

  // Shutdown user cloud policy.
  if (user_cloud_policy_subsystem_.get())
    user_cloud_policy_subsystem_->Shutdown();
  user_cloud_policy_subsystem_.reset();
  user_policy_token_cache_.reset();
  user_data_store_.reset();
}

void BrowserPolicyConnector::Init() {
  managed_platform_provider_.reset(CreateManagedPlatformProvider());
  recommended_platform_provider_.reset(CreateRecommendedPlatformProvider());

  managed_cloud_provider_.reset(new CloudPolicyProviderImpl(
      this,
      GetChromePolicyDefinitionList(),
      POLICY_LEVEL_MANDATORY));
  recommended_cloud_provider_.reset(new CloudPolicyProviderImpl(
      this,
      GetChromePolicyDefinitionList(),
      POLICY_LEVEL_RECOMMENDED));

  // |providers| in decreasing order of priority.
  PolicyServiceImpl::Providers providers;
  providers.push_back(managed_platform_provider_.get());
  providers.push_back(managed_cloud_provider_.get());
  providers.push_back(recommended_platform_provider_.get());
  providers.push_back(recommended_cloud_provider_.get());
  policy_service_.reset(new PolicyServiceImpl(providers));

#if defined(OS_CHROMEOS)
  InitializeDevicePolicy();

  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableONCPolicy)) {
    network_configuration_updater_.reset(
        new NetworkConfigurationUpdater(
            managed_cloud_provider_.get(),
            chromeos::CrosLibrary::Get()->GetNetworkLibrary()));
  }

  // Create the AppPackUpdater to start updating the cache. It requires the
  // system request context, which isn't available yet; therefore it is
  // created only once the loops are running.
  MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(base::IgnoreResult(&BrowserPolicyConnector::GetAppPackUpdater),
                 weak_ptr_factory_.GetWeakPtr()));
#endif
}

ConfigurationPolicyProvider*
    BrowserPolicyConnector::GetManagedPlatformProvider() const {
  return managed_platform_provider_.get();
}

ConfigurationPolicyProvider*
    BrowserPolicyConnector::GetManagedCloudProvider() const {
  return managed_cloud_provider_.get();
}

ConfigurationPolicyProvider*
    BrowserPolicyConnector::GetRecommendedPlatformProvider() const {
  return recommended_platform_provider_.get();
}

ConfigurationPolicyProvider*
    BrowserPolicyConnector::GetRecommendedCloudProvider() const {
  return recommended_cloud_provider_.get();
}

PolicyService* BrowserPolicyConnector::GetPolicyService() const {
  return policy_service_.get();
}

void BrowserPolicyConnector::RegisterForDevicePolicy(
    const std::string& owner_email,
    const std::string& token,
    bool known_machine_id) {
#if defined(OS_CHROMEOS)
  if (device_data_store_.get()) {
    if (!device_data_store_->device_token().empty()) {
      LOG(ERROR) << "Device policy data store already has a DMToken; "
                 << "RegisterForDevicePolicy won't trigger a new registration.";
    }
    device_data_store_->set_user_name(owner_email);
    device_data_store_->set_known_machine_id(known_machine_id);
    device_data_store_->SetOAuthToken(token);
  }
#endif
}

bool BrowserPolicyConnector::IsEnterpriseManaged() {
#if defined(OS_CHROMEOS)
  return install_attributes_.get() && install_attributes_->IsEnterpriseDevice();
#else
  return false;
#endif
}

EnterpriseInstallAttributes::LockResult
    BrowserPolicyConnector::LockDevice(const std::string& user) {
#if defined(OS_CHROMEOS)
  if (install_attributes_.get()) {
    return install_attributes_->LockDevice(user,
                                           device_data_store_->device_mode(),
                                           device_data_store_->device_id());
  }
#endif

  return EnterpriseInstallAttributes::LOCK_BACKEND_ERROR;
}

// static
std::string BrowserPolicyConnector::GetSerialNumber() {
  std::string serial_number;
#if defined(OS_CHROMEOS)
  chromeos::system::StatisticsProvider* provider =
      chromeos::system::StatisticsProvider::GetInstance();
  for (size_t i = 0; i < arraysize(kMachineInfoSerialNumberKeys); i++) {
    if (provider->GetMachineStatistic(kMachineInfoSerialNumberKeys[i],
                                      &serial_number) &&
        !serial_number.empty()) {
      break;
    }
  }
#endif
  return serial_number;
}

std::string BrowserPolicyConnector::GetEnterpriseDomain() {
#if defined(OS_CHROMEOS)
  if (install_attributes_.get())
    return install_attributes_->GetDomain();
#endif

  return std::string();
}

DeviceMode BrowserPolicyConnector::GetDeviceMode() {
#if defined(OS_CHROMEOS)
  if (install_attributes_.get())
    return install_attributes_->GetMode();
  else
    return DEVICE_MODE_UNKNOWN;
#endif

  // We only have the notion of "enterprise" device on ChromeOS for now.
  return DEVICE_MODE_CONSUMER;
}

void BrowserPolicyConnector::ResetDevicePolicy() {
#if defined(OS_CHROMEOS)
  if (device_cloud_policy_subsystem_.get())
    device_cloud_policy_subsystem_->Reset();
#endif
}

void BrowserPolicyConnector::FetchCloudPolicy() {
#if defined(OS_CHROMEOS)
  if (device_cloud_policy_subsystem_.get())
    device_cloud_policy_subsystem_->RefreshPolicies(false);
  if (user_cloud_policy_subsystem_.get())
    user_cloud_policy_subsystem_->RefreshPolicies(true);  // wait_for_auth_token
#endif
}

void BrowserPolicyConnector::RefreshPolicies() {
  if (managed_platform_provider_.get())
    managed_platform_provider_->RefreshPolicies();
  if (recommended_platform_provider_.get())
    recommended_platform_provider_->RefreshPolicies();
  if (managed_cloud_provider_.get())
    managed_cloud_provider_->RefreshPolicies();
  if (recommended_cloud_provider_.get())
    recommended_cloud_provider_->RefreshPolicies();
}

void BrowserPolicyConnector::ScheduleServiceInitialization(
    int64 delay_milliseconds) {
  if (user_cloud_policy_subsystem_.get()) {
    user_cloud_policy_subsystem_->
        ScheduleServiceInitialization(delay_milliseconds);
  }
#if defined(OS_CHROMEOS)
  if (device_cloud_policy_subsystem_.get()) {
    device_cloud_policy_subsystem_->
        ScheduleServiceInitialization(delay_milliseconds);
  }
#endif
}
void BrowserPolicyConnector::InitializeUserPolicy(
    const std::string& user_name,
    bool wait_for_policy_fetch) {
  // Throw away the old backend.
  user_cloud_policy_subsystem_.reset();
  user_policy_token_cache_.reset();
  user_data_store_.reset();
  token_service_ = NULL;
  registrar_.RemoveAll();

  CommandLine* command_line = CommandLine::ForCurrentProcess();

  FilePath policy_dir;
  PathService::Get(chrome::DIR_USER_DATA, &policy_dir);
#if defined(OS_CHROMEOS)
  policy_dir = policy_dir.Append(
      command_line->GetSwitchValuePath(switches::kLoginProfile));
#endif

  if (command_line->HasSwitch(switches::kDeviceManagementUrl)) {
    FilePath policy_cache_dir = policy_dir.Append(kPolicyDir);

    UserPolicyCache* user_policy_cache =
        new UserPolicyCache(policy_cache_dir.Append(kPolicyCacheFile),
                            wait_for_policy_fetch);
    user_data_store_.reset(CloudPolicyDataStore::CreateForUserPolicies());
    user_policy_token_cache_.reset(
        new UserPolicyTokenCache(user_data_store_.get(),
                                 policy_cache_dir.Append(kTokenCacheFile)));

    // Prepending user caches meaning they will take precedence of device policy
    // caches.
    managed_cloud_provider_->PrependCache(user_policy_cache);
    recommended_cloud_provider_->PrependCache(user_policy_cache);
    user_cloud_policy_subsystem_.reset(new CloudPolicySubsystem(
        user_data_store_.get(),
        user_policy_cache));

    // Initiate the DM-Token load.
    user_policy_token_cache_->Load();

    user_data_store_->set_user_name(user_name);
    user_data_store_->set_user_affiliation(GetUserAffiliation(user_name));

    int64 delay =
        wait_for_policy_fetch ? 0 : kServiceInitializationStartupDelay;
    user_cloud_policy_subsystem_->CompleteInitialization(
        prefs::kUserPolicyRefreshRate,
        delay);
  }
}

void BrowserPolicyConnector::SetUserPolicyTokenService(
    TokenService* token_service) {
  token_service_ = token_service;
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_AVAILABLE,
                 content::Source<TokenService>(token_service_));

  if (token_service_->HasTokenForService(
          GaiaConstants::kDeviceManagementService)) {
    user_data_store_->SetGaiaToken(token_service_->GetTokenForService(
        GaiaConstants::kDeviceManagementService));
  }
}

void BrowserPolicyConnector::RegisterForUserPolicy(
    const std::string& oauth_token) {
  if (oauth_token.empty()) {
    // An attempt to fetch the dm service oauth token has failed. Notify
    // the user policy cache of this, so that a potential blocked login
    // proceeds without waiting for user policy.
    if (user_cloud_policy_subsystem_.get()) {
      user_cloud_policy_subsystem_->GetCloudPolicyCacheBase()->
          SetFetchingDone();
    }
  } else {
    if (user_data_store_.get())
      user_data_store_->SetOAuthToken(oauth_token);
  }
}

CloudPolicyDataStore* BrowserPolicyConnector::GetDeviceCloudPolicyDataStore() {
#if defined(OS_CHROMEOS)
  return device_data_store_.get();
#else
  return NULL;
#endif
}

CloudPolicyDataStore* BrowserPolicyConnector::GetUserCloudPolicyDataStore() {
  return user_data_store_.get();
}

const ConfigurationPolicyHandlerList*
    BrowserPolicyConnector::GetHandlerList() const {
  return &handler_list_;
}

UserAffiliation BrowserPolicyConnector::GetUserAffiliation(
    const std::string& user_name) {
#if defined(OS_CHROMEOS)
  if (install_attributes_.get()) {
    size_t pos = user_name.find('@');
    if (pos != std::string::npos &&
        user_name.substr(pos + 1) == install_attributes_->GetDomain()) {
      return USER_AFFILIATION_MANAGED;
    }
  }
#endif

  return USER_AFFILIATION_NONE;
}

AppPackUpdater* BrowserPolicyConnector::GetAppPackUpdater() {
#if defined(OS_CHROMEOS)
  if (!app_pack_updater_.get()) {
    // system_request_context() is NULL in unit tests.
    net::URLRequestContextGetter* request_context =
        g_browser_process->system_request_context();
    if (request_context)
      app_pack_updater_.reset(new AppPackUpdater(request_context, this));
  }
  return app_pack_updater_.get();
#else
  return NULL;
#endif
}

void BrowserPolicyConnector::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
    const TokenService* token_source =
        content::Source<const TokenService>(source).ptr();
    DCHECK_EQ(token_service_, token_source);
    const TokenService::TokenAvailableDetails* token_details =
        content::Details<const TokenService::TokenAvailableDetails>(details).
            ptr();
    if (token_details->service() == GaiaConstants::kDeviceManagementService) {
      if (user_data_store_.get()) {
        user_data_store_->SetGaiaToken(token_details->token());
      }
    }
  } else {
    NOTREACHED();
  }
}

void BrowserPolicyConnector::InitializeDevicePolicy() {
#if defined(OS_CHROMEOS)
  // Throw away the old backend.
  device_cloud_policy_subsystem_.reset();
  device_data_store_.reset();

  CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kEnableDevicePolicy)) {
    device_data_store_.reset(CloudPolicyDataStore::CreateForDevicePolicies());
    chromeos::CryptohomeLibrary* cryptohome =
        chromeos::CrosLibrary::Get()->GetCryptohomeLibrary();
    install_attributes_.reset(new EnterpriseInstallAttributes(cryptohome));
    DevicePolicyCache* device_policy_cache =
        new DevicePolicyCache(device_data_store_.get(),
                              install_attributes_.get());

    managed_cloud_provider_->AppendCache(device_policy_cache);
    recommended_cloud_provider_->AppendCache(device_policy_cache);

    device_cloud_policy_subsystem_.reset(new CloudPolicySubsystem(
        device_data_store_.get(),
        device_policy_cache));

    // Initialize the subsystem once the message loops are spinning.
    MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&BrowserPolicyConnector::CompleteInitialization,
                   weak_ptr_factory_.GetWeakPtr()));
  }
#endif
}

void BrowserPolicyConnector::CompleteInitialization() {
#if defined(OS_CHROMEOS)
  if (device_cloud_policy_subsystem_.get()) {
    // Read serial number and machine model. This must be done before we call
    // CompleteInitialization() below such that the serial number is available
    // for re-submission in case we're doing serial number recovery.
    if (device_data_store_->machine_id().empty() ||
        device_data_store_->machine_model().empty()) {
      chromeos::system::StatisticsProvider* provider =
          chromeos::system::StatisticsProvider::GetInstance();

      std::string machine_model;
      if (!provider->GetMachineStatistic(kMachineInfoSystemHwqual,
                                         &machine_model)) {
        LOG(ERROR) << "Failed to get machine model.";
      }

      std::string machine_id = GetSerialNumber();
      if (machine_id.empty())
        LOG(ERROR) << "Failed to get machine serial number.";

      device_data_store_->set_machine_id(machine_id);
      device_data_store_->set_machine_model(machine_model);
    }

    device_cloud_policy_subsystem_->CompleteInitialization(
        prefs::kDevicePolicyRefreshRate,
        kServiceInitializationStartupDelay);
  }
  device_data_store_->set_device_status_collector(
      new DeviceStatusCollector(
          g_browser_process->local_state(),
          chromeos::system::StatisticsProvider::GetInstance()));
#endif
}

// static
ConfigurationPolicyProvider*
    BrowserPolicyConnector::CreateManagedPlatformProvider() {
  const PolicyDefinitionList* policy_list = GetChromePolicyDefinitionList();
#if defined(OS_WIN)
  return new ConfigurationPolicyProviderWin(policy_list,
                                            policy::kRegistryMandatorySubKey,
                                            POLICY_LEVEL_MANDATORY);
#elif defined(OS_MACOSX)
  return new ConfigurationPolicyProviderMac(policy_list,
                                            POLICY_LEVEL_MANDATORY);
#elif defined(OS_POSIX)
  FilePath config_dir_path;
  if (PathService::Get(chrome::DIR_POLICY_FILES, &config_dir_path)) {
    return new ConfigDirPolicyProvider(
        policy_list,
        POLICY_LEVEL_MANDATORY,
        POLICY_SCOPE_MACHINE,
        config_dir_path.Append(FILE_PATH_LITERAL("managed")));
  } else {
    return NULL;
  }
#else
  return NULL;
#endif
}

// static
ConfigurationPolicyProvider*
    BrowserPolicyConnector::CreateRecommendedPlatformProvider() {
  const PolicyDefinitionList* policy_list = GetChromePolicyDefinitionList();
#if defined(OS_WIN)
  return new ConfigurationPolicyProviderWin(policy_list,
                                            policy::kRegistryRecommendedSubKey,
                                            POLICY_LEVEL_RECOMMENDED);
#elif defined(OS_MACOSX)
  return new ConfigurationPolicyProviderMac(policy_list,
                                            POLICY_LEVEL_RECOMMENDED);
#elif defined(OS_POSIX)
  FilePath config_dir_path;
  if (PathService::Get(chrome::DIR_POLICY_FILES, &config_dir_path)) {
    return new ConfigDirPolicyProvider(
        policy_list,
        POLICY_LEVEL_RECOMMENDED,
        POLICY_SCOPE_MACHINE,
        config_dir_path.Append(FILE_PATH_LITERAL("recommended")));
  } else {
    return NULL;
  }
#else
  return NULL;
#endif
}

}  // namespace policy