Home | History | Annotate | Download | only in common
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/policy/core/common/config_dir_policy_loader.h"
      6 
      7 #include <algorithm>
      8 #include <set>
      9 #include <string>
     10 
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/file_util.h"
     14 #include "base/files/file_enumerator.h"
     15 #include "base/json/json_file_value_serializer.h"
     16 #include "base/json/json_reader.h"
     17 #include "base/logging.h"
     18 #include "base/stl_util.h"
     19 #include "components/policy/core/common/policy_bundle.h"
     20 #include "components/policy/core/common/policy_load_status.h"
     21 
     22 namespace policy {
     23 
     24 namespace {
     25 
     26 // Subdirectories that contain the mandatory and recommended policies.
     27 const base::FilePath::CharType kMandatoryConfigDir[] =
     28     FILE_PATH_LITERAL("managed");
     29 const base::FilePath::CharType kRecommendedConfigDir[] =
     30     FILE_PATH_LITERAL("recommended");
     31 
     32 PolicyLoadStatus JsonErrorToPolicyLoadStatus(int status) {
     33   switch (status) {
     34     case JSONFileValueSerializer::JSON_ACCESS_DENIED:
     35     case JSONFileValueSerializer::JSON_CANNOT_READ_FILE:
     36     case JSONFileValueSerializer::JSON_FILE_LOCKED:
     37       return POLICY_LOAD_STATUS_READ_ERROR;
     38     case JSONFileValueSerializer::JSON_NO_SUCH_FILE:
     39       return POLICY_LOAD_STATUS_MISSING;
     40     case base::JSONReader::JSON_INVALID_ESCAPE:
     41     case base::JSONReader::JSON_SYNTAX_ERROR:
     42     case base::JSONReader::JSON_UNEXPECTED_TOKEN:
     43     case base::JSONReader::JSON_TRAILING_COMMA:
     44     case base::JSONReader::JSON_TOO_MUCH_NESTING:
     45     case base::JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT:
     46     case base::JSONReader::JSON_UNSUPPORTED_ENCODING:
     47     case base::JSONReader::JSON_UNQUOTED_DICTIONARY_KEY:
     48       return POLICY_LOAD_STATUS_PARSE_ERROR;
     49     case base::JSONReader::JSON_NO_ERROR:
     50       NOTREACHED();
     51       return POLICY_LOAD_STATUS_STARTED;
     52   }
     53   NOTREACHED() << "Invalid status " << status;
     54   return POLICY_LOAD_STATUS_PARSE_ERROR;
     55 }
     56 
     57 }  // namespace
     58 
     59 ConfigDirPolicyLoader::ConfigDirPolicyLoader(
     60     scoped_refptr<base::SequencedTaskRunner> task_runner,
     61     const base::FilePath& config_dir,
     62     PolicyScope scope)
     63     : AsyncPolicyLoader(task_runner), config_dir_(config_dir), scope_(scope) {}
     64 
     65 ConfigDirPolicyLoader::~ConfigDirPolicyLoader() {}
     66 
     67 void ConfigDirPolicyLoader::InitOnBackgroundThread() {
     68   base::FilePathWatcher::Callback callback =
     69       base::Bind(&ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
     70   mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir), false,
     71                            callback);
     72   recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir), false,
     73                              callback);
     74 }
     75 
     76 scoped_ptr<PolicyBundle> ConfigDirPolicyLoader::Load() {
     77   scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
     78   LoadFromPath(config_dir_.Append(kMandatoryConfigDir),
     79                POLICY_LEVEL_MANDATORY,
     80                bundle.get());
     81   LoadFromPath(config_dir_.Append(kRecommendedConfigDir),
     82                POLICY_LEVEL_RECOMMENDED,
     83                bundle.get());
     84   return bundle.Pass();
     85 }
     86 
     87 base::Time ConfigDirPolicyLoader::LastModificationTime() {
     88   static const base::FilePath::CharType* kConfigDirSuffixes[] = {
     89     kMandatoryConfigDir,
     90     kRecommendedConfigDir,
     91   };
     92 
     93   base::Time last_modification = base::Time();
     94   base::File::Info info;
     95 
     96   for (size_t i = 0; i < arraysize(kConfigDirSuffixes); ++i) {
     97     base::FilePath path(config_dir_.Append(kConfigDirSuffixes[i]));
     98 
     99     // Skip if the file doesn't exist, or it isn't a directory.
    100     if (!base::GetFileInfo(path, &info) || !info.is_directory)
    101       continue;
    102 
    103     // Enumerate the files and find the most recent modification timestamp.
    104     base::FileEnumerator file_enumerator(path, false,
    105                                          base::FileEnumerator::FILES);
    106     for (base::FilePath config_file = file_enumerator.Next();
    107          !config_file.empty();
    108          config_file = file_enumerator.Next()) {
    109       if (base::GetFileInfo(config_file, &info) && !info.is_directory)
    110         last_modification = std::max(last_modification, info.last_modified);
    111     }
    112   }
    113 
    114   return last_modification;
    115 }
    116 
    117 void ConfigDirPolicyLoader::LoadFromPath(const base::FilePath& path,
    118                                          PolicyLevel level,
    119                                          PolicyBundle* bundle) {
    120   // Enumerate the files and sort them lexicographically.
    121   std::set<base::FilePath> files;
    122   base::FileEnumerator file_enumerator(path, false,
    123                                        base::FileEnumerator::FILES);
    124   for (base::FilePath config_file_path = file_enumerator.Next();
    125        !config_file_path.empty(); config_file_path = file_enumerator.Next())
    126     files.insert(config_file_path);
    127 
    128   PolicyLoadStatusSample status;
    129   if (files.empty()) {
    130     status.Add(POLICY_LOAD_STATUS_NO_POLICY);
    131     return;
    132   }
    133 
    134   // Start with an empty dictionary and merge the files' contents.
    135   // The files are processed in reverse order because |MergeFrom| gives priority
    136   // to existing keys, but the ConfigDirPolicyProvider gives priority to the
    137   // last file in lexicographic order.
    138   for (std::set<base::FilePath>::reverse_iterator config_file_iter =
    139            files.rbegin(); config_file_iter != files.rend();
    140        ++config_file_iter) {
    141     JSONFileValueSerializer deserializer(*config_file_iter);
    142     deserializer.set_allow_trailing_comma(true);
    143     int error_code = 0;
    144     std::string error_msg;
    145     scoped_ptr<base::Value> value(
    146         deserializer.Deserialize(&error_code, &error_msg));
    147     if (!value.get()) {
    148       LOG(WARNING) << "Failed to read configuration file "
    149                    << config_file_iter->value() << ": " << error_msg;
    150       status.Add(JsonErrorToPolicyLoadStatus(error_code));
    151       continue;
    152     }
    153     base::DictionaryValue* dictionary_value = NULL;
    154     if (!value->GetAsDictionary(&dictionary_value)) {
    155       LOG(WARNING) << "Expected JSON dictionary in configuration file "
    156                    << config_file_iter->value();
    157       status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
    158       continue;
    159     }
    160 
    161     // Detach the "3rdparty" node.
    162     scoped_ptr<base::Value> third_party;
    163     if (dictionary_value->Remove("3rdparty", &third_party))
    164       Merge3rdPartyPolicy(third_party.get(), level, bundle);
    165 
    166     // Add chrome policy.
    167     PolicyMap policy_map;
    168     policy_map.LoadFrom(dictionary_value, level, scope_);
    169     bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
    170         .MergeFrom(policy_map);
    171   }
    172 }
    173 
    174 void ConfigDirPolicyLoader::Merge3rdPartyPolicy(
    175     const base::Value* policies,
    176     PolicyLevel level,
    177     PolicyBundle* bundle) {
    178   // The first-level entries in |policies| are PolicyDomains. The second-level
    179   // entries are component IDs, and the third-level entries are the policies
    180   // for that domain/component namespace.
    181 
    182   const base::DictionaryValue* domains_dictionary;
    183   if (!policies->GetAsDictionary(&domains_dictionary)) {
    184     LOG(WARNING) << "3rdparty value is not a dictionary!";
    185     return;
    186   }
    187 
    188   // Helper to lookup a domain given its string name.
    189   std::map<std::string, PolicyDomain> supported_domains;
    190   supported_domains["extensions"] = POLICY_DOMAIN_EXTENSIONS;
    191 
    192   for (base::DictionaryValue::Iterator domains_it(*domains_dictionary);
    193        !domains_it.IsAtEnd(); domains_it.Advance()) {
    194     if (!ContainsKey(supported_domains, domains_it.key())) {
    195       LOG(WARNING) << "Unsupported 3rd party policy domain: "
    196                    << domains_it.key();
    197       continue;
    198     }
    199 
    200     const base::DictionaryValue* components_dictionary;
    201     if (!domains_it.value().GetAsDictionary(&components_dictionary)) {
    202       LOG(WARNING) << "3rdparty/" << domains_it.key()
    203                    << " value is not a dictionary!";
    204       continue;
    205     }
    206 
    207     PolicyDomain domain = supported_domains[domains_it.key()];
    208     for (base::DictionaryValue::Iterator components_it(*components_dictionary);
    209          !components_it.IsAtEnd(); components_it.Advance()) {
    210       const base::DictionaryValue* policy_dictionary;
    211       if (!components_it.value().GetAsDictionary(&policy_dictionary)) {
    212         LOG(WARNING) << "3rdparty/" << domains_it.key() << "/"
    213                      << components_it.key() << " value is not a dictionary!";
    214         continue;
    215       }
    216 
    217       PolicyMap policy;
    218       policy.LoadFrom(policy_dictionary, level, scope_);
    219       bundle->Get(PolicyNamespace(domain, components_it.key()))
    220           .MergeFrom(policy);
    221     }
    222   }
    223 }
    224 
    225 void ConfigDirPolicyLoader::OnFileUpdated(const base::FilePath& path,
    226                                           bool error) {
    227   if (!error)
    228     Reload(false);
    229 }
    230 
    231 }  // namespace policy
    232