// 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/ui/webui/policy_ui.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/policy/browser_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/policy/schema_registry_service.h"
#include "chrome/browser/policy/schema_registry_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h"
#include "components/policy/core/browser/cloud/message_util.h"
#include "components/policy/core/browser/configuration_policy_handler_list.h"
#include "components/policy/core/browser/policy_error_map.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/cloud/cloud_policy_store.h"
#include "components/policy/core/common/cloud/cloud_policy_validator.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/core/common/schema_map.h"
#include "components/policy/core/common/schema_registry.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "grit/browser_resources.h"
#include "grit/component_strings.h"
#include "policy/policy_constants.h"
#include "policy/proto/device_management_backend.pb.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
#include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
#include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
#include "chrome/browser/chromeos/policy/user_cloud_policy_manager_factory_chromeos.h"
#else
#include "chrome/browser/policy/cloud/user_cloud_policy_manager_factory.h"
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#include "content/public/browser/web_contents.h"
#endif

#if !defined(OS_ANDROID) && !defined(OS_IOS)
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/common/extensions/extension_set.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#endif

namespace em = enterprise_management;

namespace {

content::WebUIDataSource* CreatePolicyUIHTMLSource() {
  content::WebUIDataSource* source =
      content::WebUIDataSource::Create(chrome::kChromeUIPolicyHost);

  // Localized strings.
  source->AddLocalizedString("title", IDS_POLICY_TITLE);
  source->AddLocalizedString("filterPlaceholder",
                             IDS_POLICY_FILTER_PLACEHOLDER);
  source->AddLocalizedString("reloadPolicies", IDS_POLICY_RELOAD_POLICIES);
  source->AddLocalizedString("status", IDS_POLICY_STATUS);
  source->AddLocalizedString("statusDevice", IDS_POLICY_STATUS_DEVICE);
  source->AddLocalizedString("statusUser", IDS_POLICY_STATUS_USER);
  source->AddLocalizedString("labelDomain", IDS_POLICY_LABEL_DOMAIN);
  source->AddLocalizedString("labelUsername", IDS_POLICY_LABEL_USERNAME);
  source->AddLocalizedString("labelClientId", IDS_POLICY_LABEL_CLIENT_ID);
  source->AddLocalizedString("labelTimeSinceLastRefresh",
                             IDS_POLICY_LABEL_TIME_SINCE_LAST_REFRESH);
  source->AddLocalizedString("labelRefreshInterval",
                             IDS_POLICY_LABEL_REFRESH_INTERVAL);
  source->AddLocalizedString("labelStatus", IDS_POLICY_LABEL_STATUS);
  source->AddLocalizedString("showUnset", IDS_POLICY_SHOW_UNSET);
  source->AddLocalizedString("noPoliciesSet", IDS_POLICY_NO_POLICIES_SET);
  source->AddLocalizedString("headerScope", IDS_POLICY_HEADER_SCOPE);
  source->AddLocalizedString("headerLevel", IDS_POLICY_HEADER_LEVEL);
  source->AddLocalizedString("headerName", IDS_POLICY_HEADER_NAME);
  source->AddLocalizedString("headerValue", IDS_POLICY_HEADER_VALUE);
  source->AddLocalizedString("headerStatus", IDS_POLICY_HEADER_STATUS);
  source->AddLocalizedString("showExpandedValue",
                             IDS_POLICY_SHOW_EXPANDED_VALUE);
  source->AddLocalizedString("hideExpandedValue",
                             IDS_POLICY_HIDE_EXPANDED_VALUE);
  source->AddLocalizedString("scopeUser", IDS_POLICY_SCOPE_USER);
  source->AddLocalizedString("scopeDevice", IDS_POLICY_SCOPE_DEVICE);
  source->AddLocalizedString("levelRecommended", IDS_POLICY_LEVEL_RECOMMENDED);
  source->AddLocalizedString("levelMandatory", IDS_POLICY_LEVEL_MANDATORY);
  source->AddLocalizedString("ok", IDS_POLICY_OK);
  source->AddLocalizedString("unset", IDS_POLICY_UNSET);
  source->AddLocalizedString("unknown", IDS_POLICY_UNKNOWN);

  source->SetUseJsonJSFormatV2();
  source->SetJsonPath("strings.js");

  // Add required resources.
  source->AddResourcePath("policy.css", IDR_POLICY_CSS);
  source->AddResourcePath("policy.js", IDR_POLICY_JS);
  source->AddResourcePath("uber_utils.js", IDR_UBER_UTILS_JS);
  source->SetDefaultResource(IDR_POLICY_HTML);

  return source;
}

void GetStatusFromCore(const policy::CloudPolicyCore* core,
                       base::DictionaryValue* dict) {
  const policy::CloudPolicyStore* store = core->store();
  const policy::CloudPolicyClient* client = core->client();
  const policy::CloudPolicyRefreshScheduler* refresh_scheduler =
        core->refresh_scheduler();

  bool no_error = store->status() == policy::CloudPolicyStore::STATUS_OK &&
                  client && client->status() == policy::DM_STATUS_SUCCESS;
  base::string16 status =
      store->status() == policy::CloudPolicyStore::STATUS_OK &&
      client && client->status() != policy::DM_STATUS_SUCCESS ?
                policy::FormatDeviceManagementStatus(client->status()) :
                policy::FormatStoreStatus(store->status(),
                                          store->validation_status());
  const em::PolicyData* policy = store->policy();
  std::string client_id = policy ? policy->device_id() : std::string();
  std::string username = policy ? policy->username() : std::string();
  base::TimeDelta refresh_interval =
      base::TimeDelta::FromMilliseconds(refresh_scheduler ?
          refresh_scheduler->refresh_delay() :
          policy::CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs);
  base::Time last_refresh_time = refresh_scheduler ?
      refresh_scheduler->last_refresh() : base::Time();

  dict->SetBoolean("error", !no_error);
  dict->SetString("status", status);
  dict->SetString("clientId", client_id);
  dict->SetString("username", username);
  dict->SetString("refreshInterval",
                  ui::TimeFormat::TimeRemainingShort(refresh_interval));
  dict->SetString("timeSinceLastRefresh", last_refresh_time.is_null() ?
      l10n_util::GetStringUTF16(IDS_POLICY_NEVER_FETCHED) :
      ui::TimeFormat::TimeElapsed(base::Time::NowFromSystemTime() -
                                  last_refresh_time));
}

void ExtractDomainFromUsername(base::DictionaryValue* dict) {
  std::string username;
  dict->GetString("username", &username);
  if (!username.empty())
    dict->SetString("domain", gaia::ExtractDomainName(username));
}

// Utility function that returns a JSON serialization of the given |dict|.
scoped_ptr<base::StringValue> DictionaryToJSONString(
    const base::DictionaryValue* dict) {
  std::string json_string;
  base::JSONWriter::WriteWithOptions(dict,
                                     base::JSONWriter::OPTIONS_PRETTY_PRINT,
                                     &json_string);
  return make_scoped_ptr(new base::StringValue(json_string));
}

// Returns a copy of |value| with some values converted to a representation that
// i18n_template.js will display in a nicer way.
scoped_ptr<base::Value> CopyAndConvert(const base::Value* value) {
  const base::DictionaryValue* dict = NULL;
  if (value->GetAsDictionary(&dict))
    return DictionaryToJSONString(dict).PassAs<base::Value>();

  scoped_ptr<base::Value> copy(value->DeepCopy());
  base::ListValue* list = NULL;
  if (copy->GetAsList(&list)) {
    for (size_t i = 0; i < list->GetSize(); ++i) {
      if (list->GetDictionary(i, &dict))
        list->Set(i, DictionaryToJSONString(dict).release());
    }
  }

  return copy.Pass();
}

}  // namespace

// An interface for querying the status of cloud policy.
class CloudPolicyStatusProvider {
 public:
  CloudPolicyStatusProvider();
  virtual ~CloudPolicyStatusProvider();

  // Sets a callback to invoke upon status changes.
  void SetStatusChangeCallback(const base::Closure& callback);

  virtual void GetStatus(base::DictionaryValue* dict);

 protected:
  void NotifyStatusChange();

 private:
  base::Closure callback_;

  DISALLOW_COPY_AND_ASSIGN(CloudPolicyStatusProvider);
};

// Status provider implementation that pulls cloud policy status from a
// CloudPolicyCore instance provided at construction time. Also listens for
// changes on that CloudPolicyCore and reports them through the status change
// callback.
class CloudPolicyCoreStatusProvider
    : public CloudPolicyStatusProvider,
      public policy::CloudPolicyStore::Observer {
 public:
  explicit CloudPolicyCoreStatusProvider(policy::CloudPolicyCore* core);
  virtual ~CloudPolicyCoreStatusProvider();

  // policy::CloudPolicyStore::Observer implementation.
  virtual void OnStoreLoaded(policy::CloudPolicyStore* store) OVERRIDE;
  virtual void OnStoreError(policy::CloudPolicyStore* store) OVERRIDE;

 protected:
  // Policy status is read from the CloudPolicyClient, CloudPolicyStore and
  // CloudPolicyRefreshScheduler hosted by this |core_|.
  policy::CloudPolicyCore* core_;

  DISALLOW_COPY_AND_ASSIGN(CloudPolicyCoreStatusProvider);
};

// A cloud policy status provider for user policy.
class UserPolicyStatusProvider : public CloudPolicyCoreStatusProvider {
 public:
  explicit UserPolicyStatusProvider(policy::CloudPolicyCore* core);
  virtual ~UserPolicyStatusProvider();

  // CloudPolicyCoreStatusProvider implementation.
  virtual void GetStatus(base::DictionaryValue* dict) OVERRIDE;

 private:
  DISALLOW_COPY_AND_ASSIGN(UserPolicyStatusProvider);
};

#if defined(OS_CHROMEOS)
// A cloud policy status provider for device policy.
class DevicePolicyStatusProvider : public CloudPolicyCoreStatusProvider {
 public:
  explicit DevicePolicyStatusProvider(
      policy::BrowserPolicyConnector* connector);
  virtual ~DevicePolicyStatusProvider();

  // CloudPolicyCoreStatusProvider implementation.
  virtual void GetStatus(base::DictionaryValue* dict) OVERRIDE;

 private:
  std::string domain_;

  DISALLOW_COPY_AND_ASSIGN(DevicePolicyStatusProvider);
};

// A cloud policy status provider that reads policy status from the policy core
// associated with the device-local account specified by |user_id| at
// construction time. The indirection via user ID and
// DeviceLocalAccountPolicyService is necessary because the device-local account
// may go away any time behind the scenes, at which point the status message
// text will indicate CloudPolicyStore::STATUS_BAD_STATE.
class DeviceLocalAccountPolicyStatusProvider
    : public CloudPolicyStatusProvider,
      public policy::DeviceLocalAccountPolicyService::Observer {
 public:
  DeviceLocalAccountPolicyStatusProvider(
      const std::string& user_id,
      policy::DeviceLocalAccountPolicyService* service);
  virtual ~DeviceLocalAccountPolicyStatusProvider();

  // CloudPolicyStatusProvider implementation.
  virtual void GetStatus(base::DictionaryValue* dict) OVERRIDE;

  // policy::DeviceLocalAccountPolicyService::Observer implementation.
  virtual void OnPolicyUpdated(const std::string& user_id) OVERRIDE;
  virtual void OnDeviceLocalAccountsChanged() OVERRIDE;

 private:
  const std::string user_id_;
  policy::DeviceLocalAccountPolicyService* service_;

  DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyStatusProvider);
};
#endif

// The JavaScript message handler for the chrome://policy page.
class PolicyUIHandler : public content::NotificationObserver,
                        public content::WebUIMessageHandler,
                        public policy::PolicyService::Observer {
 public:
  PolicyUIHandler();
  virtual ~PolicyUIHandler();

  // content::NotificationObserver implementation.
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE;

  // content::WebUIMessageHandler implementation.
  virtual void RegisterMessages() OVERRIDE;

  // policy::PolicyService::Observer implementation.
  virtual void OnPolicyUpdated(const policy::PolicyNamespace& ns,
                               const policy::PolicyMap& previous,
                               const policy::PolicyMap& current) OVERRIDE;

 private:
  // Send a dictionary containing the names of all known policies to the UI.
  void SendPolicyNames() const;

  // Send information about the current policy values to the UI. For each policy
  // whose value has been set, a dictionary containing the value and additional
  // metadata is sent.
  void SendPolicyValues() const;

  // Send the status of cloud policy to the UI. For each scope that has cloud
  // policy enabled (device and/or user), a dictionary containing status
  // information is sent.
  void SendStatus() const;

  // Inserts a description of each policy in |policy_map| into |values|, using
  // the optional errors in |errors| to determine the status of each policy.
  void GetPolicyValues(const policy::PolicyMap& policy_map,
                       policy::PolicyErrorMap* errors,
                       base::DictionaryValue* values) const;

  void GetChromePolicyValues(base::DictionaryValue* values) const;

  void HandleInitialized(const base::ListValue* args);
  void HandleReloadPolicies(const base::ListValue* args);

  void OnRefreshPoliciesDone() const;

  policy::PolicyService* GetPolicyService() const;

  bool initialized_;
  std::string device_domain_;

  // Providers that supply status dictionaries for user and device policy,
  // respectively. These are created on initialization time as appropriate for
  // the platform (Chrome OS / desktop) and type of policy that is in effect.
  scoped_ptr<CloudPolicyStatusProvider> user_status_provider_;
  scoped_ptr<CloudPolicyStatusProvider> device_status_provider_;

  content::NotificationRegistrar registrar_;

  base::WeakPtrFactory<PolicyUIHandler> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(PolicyUIHandler);
};

CloudPolicyStatusProvider::CloudPolicyStatusProvider() {
}

CloudPolicyStatusProvider::~CloudPolicyStatusProvider() {
}

void CloudPolicyStatusProvider::SetStatusChangeCallback(
    const base::Closure& callback) {
  callback_ = callback;
}

void CloudPolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
}

void CloudPolicyStatusProvider::NotifyStatusChange() {
  if (!callback_.is_null())
    callback_.Run();
}

CloudPolicyCoreStatusProvider::CloudPolicyCoreStatusProvider(
    policy::CloudPolicyCore* core) : core_(core) {
  core_->store()->AddObserver(this);
  // TODO(bartfab): Add an observer that watches for client errors. Observing
  // core_->client() directly is not safe as the client may be destroyed and
  // (re-)created anytime if the user signs in or out on desktop platforms.
}

CloudPolicyCoreStatusProvider::~CloudPolicyCoreStatusProvider() {
  core_->store()->RemoveObserver(this);
}

void CloudPolicyCoreStatusProvider::OnStoreLoaded(
    policy::CloudPolicyStore* store) {
  NotifyStatusChange();
}

void CloudPolicyCoreStatusProvider::OnStoreError(
    policy::CloudPolicyStore* store) {
  NotifyStatusChange();
}

UserPolicyStatusProvider::UserPolicyStatusProvider(
    policy::CloudPolicyCore* core) : CloudPolicyCoreStatusProvider(core) {
}

UserPolicyStatusProvider::~UserPolicyStatusProvider() {
}

void UserPolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
  if (!core_->store()->is_managed())
    return;
  GetStatusFromCore(core_, dict);
  ExtractDomainFromUsername(dict);
}

#if defined(OS_CHROMEOS)
DevicePolicyStatusProvider::DevicePolicyStatusProvider(
    policy::BrowserPolicyConnector* connector)
      : CloudPolicyCoreStatusProvider(
            connector->GetDeviceCloudPolicyManager()->core()) {
  domain_ = connector->GetEnterpriseDomain();
}

DevicePolicyStatusProvider::~DevicePolicyStatusProvider() {
}

void DevicePolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
  GetStatusFromCore(core_, dict);
  dict->SetString("domain", domain_);
}

DeviceLocalAccountPolicyStatusProvider::DeviceLocalAccountPolicyStatusProvider(
    const std::string& user_id,
    policy::DeviceLocalAccountPolicyService* service)
      : user_id_(user_id),
        service_(service) {
  service_->AddObserver(this);
}

DeviceLocalAccountPolicyStatusProvider::
    ~DeviceLocalAccountPolicyStatusProvider() {
  service_->RemoveObserver(this);
}

void DeviceLocalAccountPolicyStatusProvider::GetStatus(
    base::DictionaryValue* dict) {
  const policy::DeviceLocalAccountPolicyBroker* broker =
      service_->GetBrokerForUser(user_id_);
  if (broker) {
    GetStatusFromCore(broker->core(), dict);
  } else {
    dict->SetBoolean("error", true);
    dict->SetString("status",
                    policy::FormatStoreStatus(
                        policy::CloudPolicyStore::STATUS_BAD_STATE,
                        policy::CloudPolicyValidatorBase::VALIDATION_OK));
    dict->SetString("username", std::string());
  }
  ExtractDomainFromUsername(dict);
  dict->SetBoolean("publicAccount", true);
}

void DeviceLocalAccountPolicyStatusProvider::OnPolicyUpdated(
    const std::string& user_id) {
  if (user_id == user_id_)
    NotifyStatusChange();
}

void DeviceLocalAccountPolicyStatusProvider::OnDeviceLocalAccountsChanged() {
  NotifyStatusChange();
}
#endif

PolicyUIHandler::PolicyUIHandler()
    : initialized_(false),
      weak_factory_(this) {
}

PolicyUIHandler::~PolicyUIHandler() {
  GetPolicyService()->RemoveObserver(policy::POLICY_DOMAIN_CHROME, this);
  GetPolicyService()->RemoveObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);
}

void PolicyUIHandler::RegisterMessages() {
#if defined(OS_CHROMEOS)
  policy::BrowserPolicyConnector* connector =
      g_browser_process->browser_policy_connector();
  if (connector->IsEnterpriseManaged())
    device_status_provider_.reset(new DevicePolicyStatusProvider(connector));

  const chromeos::UserManager* user_manager = chromeos::UserManager::Get();
  if (user_manager->IsLoggedInAsPublicAccount()) {
    policy::DeviceLocalAccountPolicyService* local_account_service =
        connector->GetDeviceLocalAccountPolicyService();
    if (local_account_service) {
      user_status_provider_.reset(
          new DeviceLocalAccountPolicyStatusProvider(
              user_manager->GetLoggedInUser()->email(), local_account_service));
    }
  } else {
    policy::UserCloudPolicyManagerChromeOS* user_cloud_policy_manager =
        policy::UserCloudPolicyManagerFactoryChromeOS::GetForProfile(
            Profile::FromWebUI(web_ui()));
    if (user_cloud_policy_manager) {
      user_status_provider_.reset(
          new UserPolicyStatusProvider(user_cloud_policy_manager->core()));
    }
  }
#else
  policy::UserCloudPolicyManager* user_cloud_policy_manager =
      policy::UserCloudPolicyManagerFactory::GetForBrowserContext(
          web_ui()->GetWebContents()->GetBrowserContext());
  if (user_cloud_policy_manager) {
    user_status_provider_.reset(
        new UserPolicyStatusProvider(user_cloud_policy_manager->core()));
  }
#endif

  if (!user_status_provider_.get())
    user_status_provider_.reset(new CloudPolicyStatusProvider());
  if (!device_status_provider_.get())
    device_status_provider_.reset(new CloudPolicyStatusProvider());

  base::Closure update_callback(base::Bind(&PolicyUIHandler::SendStatus,
                                           base::Unretained(this)));
  user_status_provider_->SetStatusChangeCallback(update_callback);
  device_status_provider_->SetStatusChangeCallback(update_callback);
  GetPolicyService()->AddObserver(policy::POLICY_DOMAIN_CHROME, this);
  GetPolicyService()->AddObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);

  registrar_.Add(this,
                 chrome::NOTIFICATION_EXTENSION_LOADED,
                 content::NotificationService::AllSources());
  registrar_.Add(this,
                 chrome::NOTIFICATION_EXTENSION_UNLOADED,
                 content::NotificationService::AllSources());

  web_ui()->RegisterMessageCallback(
      "initialized",
      base::Bind(&PolicyUIHandler::HandleInitialized, base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "reloadPolicies",
      base::Bind(&PolicyUIHandler::HandleReloadPolicies,
                 base::Unretained(this)));
}

void PolicyUIHandler::Observe(int type,
                              const content::NotificationSource& source,
                              const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED ||
         type == chrome::NOTIFICATION_EXTENSION_UNLOADED);
  SendPolicyNames();
  SendPolicyValues();
}

void PolicyUIHandler::OnPolicyUpdated(const policy::PolicyNamespace& ns,
                                      const policy::PolicyMap& previous,
                                      const policy::PolicyMap& current) {
  SendPolicyValues();
}

void PolicyUIHandler::SendPolicyNames() const {
  base::DictionaryValue names;

  Profile* profile = Profile::FromWebUI(web_ui());
  policy::SchemaRegistry* registry =
      policy::SchemaRegistryServiceFactory::GetForContext(
          profile->GetOriginalProfile());
  scoped_refptr<policy::SchemaMap> schema_map = registry->schema_map();

  // Add Chrome policy names.
  base::DictionaryValue* chrome_policy_names = new base::DictionaryValue;
  policy::PolicyNamespace chrome_ns(policy::POLICY_DOMAIN_CHROME, "");
  const policy::Schema* chrome_schema = schema_map->GetSchema(chrome_ns);
  for (policy::Schema::Iterator it = chrome_schema->GetPropertiesIterator();
       !it.IsAtEnd(); it.Advance()) {
    chrome_policy_names->SetBoolean(it.key(), true);
  }
  names.Set("chromePolicyNames", chrome_policy_names);

#if !defined(OS_ANDROID) && !defined(OS_IOS)
  // Add extension policy names.
  base::DictionaryValue* extension_policy_names = new base::DictionaryValue;

  extensions::ExtensionSystem* extension_system =
      extensions::ExtensionSystem::Get(profile);
  const ExtensionSet* extensions =
      extension_system->extension_service()->extensions();

  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end(); ++it) {
    const extensions::Extension* extension = it->get();
    // Skip this extension if it's not an enterprise extension.
    if (!extension->manifest()->HasPath(
        extensions::manifest_keys::kStorageManagedSchema))
      continue;
    base::DictionaryValue* extension_value = new base::DictionaryValue;
    extension_value->SetString("name", extension->name());
    const policy::Schema* schema =
        schema_map->GetSchema(policy::PolicyNamespace(
            policy::POLICY_DOMAIN_EXTENSIONS, extension->id()));
    base::DictionaryValue* policy_names = new base::DictionaryValue;
    if (schema && schema->valid()) {
      // Get policy names from the extension's policy schema.
      // Store in a map, not an array, for faster lookup on JS side.
      for (policy::Schema::Iterator prop = schema->GetPropertiesIterator();
           !prop.IsAtEnd(); prop.Advance()) {
        policy_names->SetBoolean(prop.key(), true);
      }
    }
    extension_value->Set("policyNames", policy_names);
    extension_policy_names->Set(extension->id(), extension_value);
  }
  names.Set("extensionPolicyNames", extension_policy_names);
#endif

  web_ui()->CallJavascriptFunction("policy.Page.setPolicyNames", names);
}

void PolicyUIHandler::SendPolicyValues() const {
  base::DictionaryValue all_policies;

  // Add Chrome policy values.
  base::DictionaryValue* chrome_policies = new base::DictionaryValue;
  GetChromePolicyValues(chrome_policies);
  all_policies.Set("chromePolicies", chrome_policies);

#if !defined(OS_ANDROID) && !defined(OS_IOS)
  // Add extension policy values.
  extensions::ExtensionSystem* extension_system =
      extensions::ExtensionSystem::Get(Profile::FromWebUI(web_ui()));
  const ExtensionSet* extensions =
      extension_system->extension_service()->extensions();
  base::DictionaryValue* extension_values = new base::DictionaryValue;

  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end(); ++it) {
    const extensions::Extension* extension = it->get();
    // Skip this extension if it's not an enterprise extension.
    if (!extension->manifest()->HasPath(
        extensions::manifest_keys::kStorageManagedSchema))
      continue;
    base::DictionaryValue* extension_policies = new base::DictionaryValue;
    policy::PolicyNamespace policy_namespace = policy::PolicyNamespace(
        policy::POLICY_DOMAIN_EXTENSIONS, extension->id());
    policy::PolicyErrorMap empty_error_map;
    GetPolicyValues(GetPolicyService()->GetPolicies(policy_namespace),
                    &empty_error_map, extension_policies);
    extension_values->Set(extension->id(), extension_policies);
  }
  all_policies.Set("extensionPolicies", extension_values);
#endif
  web_ui()->CallJavascriptFunction("policy.Page.setPolicyValues", all_policies);
}

void PolicyUIHandler::GetPolicyValues(const policy::PolicyMap& map,
                                      policy::PolicyErrorMap* errors,
                                      base::DictionaryValue* values) const {
  for (policy::PolicyMap::const_iterator entry = map.begin();
       entry != map.end(); ++entry) {
    base::DictionaryValue* value = new base::DictionaryValue;
    value->Set("value", CopyAndConvert(entry->second.value).release());
    if (entry->second.scope == policy::POLICY_SCOPE_USER)
      value->SetString("scope", "user");
    else
      value->SetString("scope", "machine");
    if (entry->second.level == policy::POLICY_LEVEL_RECOMMENDED)
      value->SetString("level", "recommended");
    else
      value->SetString("level", "mandatory");
    base::string16 error = errors->GetErrors(entry->first);
    if (!error.empty())
      value->SetString("error", error);
    values->Set(entry->first, value);
  }
}

void PolicyUIHandler::GetChromePolicyValues(
    base::DictionaryValue* values) const {
  policy::PolicyService* policy_service = GetPolicyService();
  policy::PolicyMap map;

  // Make a copy that can be modified, since some policy values are modified
  // before being displayed.
  map.CopyFrom(policy_service->GetPolicies(
      policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string())));

  // Get a list of all the errors in the policy values.
  const policy::ConfigurationPolicyHandlerList* handler_list =
      g_browser_process->browser_policy_connector()->GetHandlerList();
  policy::PolicyErrorMap errors;
  handler_list->ApplyPolicySettings(map, NULL, &errors);

  // Convert dictionary values to strings for display.
  handler_list->PrepareForDisplaying(&map);

  GetPolicyValues(map, &errors, values);
}

void PolicyUIHandler::SendStatus() const {
  scoped_ptr<base::DictionaryValue> device_status(new base::DictionaryValue);
  device_status_provider_->GetStatus(device_status.get());
  if (!device_domain_.empty())
    device_status->SetString("domain", device_domain_);
  scoped_ptr<base::DictionaryValue> user_status(new base::DictionaryValue);
  user_status_provider_->GetStatus(user_status.get());
  std::string username;
  user_status->GetString("username", &username);
  if (!username.empty())
    user_status->SetString("domain", gaia::ExtractDomainName(username));

  base::DictionaryValue status;
  if (!device_status->empty())
    status.Set("device", device_status.release());
  if (!user_status->empty())
    status.Set("user", user_status.release());

  web_ui()->CallJavascriptFunction("policy.Page.setStatus", status);
}

void PolicyUIHandler::HandleInitialized(const base::ListValue* args) {
  SendPolicyNames();
  SendPolicyValues();
  SendStatus();
}

void PolicyUIHandler::HandleReloadPolicies(const base::ListValue* args) {
  GetPolicyService()->RefreshPolicies(
      base::Bind(&PolicyUIHandler::OnRefreshPoliciesDone,
                 weak_factory_.GetWeakPtr()));
}

void PolicyUIHandler::OnRefreshPoliciesDone() const {
  web_ui()->CallJavascriptFunction("policy.Page.reloadPoliciesDone");
}

policy::PolicyService* PolicyUIHandler::GetPolicyService() const {
  return policy::ProfilePolicyConnectorFactory::GetForProfile(
      Profile::FromWebUI(web_ui()))->policy_service();
}

PolicyUI::PolicyUI(content::WebUI* web_ui) : WebUIController(web_ui) {
  web_ui->AddMessageHandler(new PolicyUIHandler);
  content::WebUIDataSource::Add(Profile::FromWebUI(web_ui),
                                CreatePolicyUIHTMLSource());
}

PolicyUI::~PolicyUI() {
}