Home | History | Annotate | Download | only in policy_hack
      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 // Most of this code is copied from various classes in
      6 // src/chrome/browser/policy. In particular, look at
      7 //
      8 //   file_based_policy_loader.{h,cc}
      9 //   config_dir_policy_provider.{h,cc}
     10 //
     11 // This is a reduction of the functionality in those classes.
     12 
     13 #include <set>
     14 
     15 #include "remoting/host/policy_hack/policy_watcher.h"
     16 
     17 #include "base/bind.h"
     18 #include "base/compiler_specific.h"
     19 #include "base/file_util.h"
     20 #include "base/files/file_enumerator.h"
     21 #include "base/files/file_path.h"
     22 #include "base/files/file_path_watcher.h"
     23 #include "base/json/json_file_value_serializer.h"
     24 #include "base/memory/scoped_ptr.h"
     25 #include "base/memory/weak_ptr.h"
     26 #include "base/single_thread_task_runner.h"
     27 #include "base/synchronization/waitable_event.h"
     28 #include "base/time/time.h"
     29 #include "base/values.h"
     30 
     31 namespace remoting {
     32 namespace policy_hack {
     33 
     34 namespace {
     35 
     36 const base::FilePath::CharType kPolicyDir[] =
     37   // Always read the Chrome policies (even on Chromium) so that policy
     38   // enforcement can't be bypassed by running Chromium.
     39   FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed");
     40 
     41 // Amount of time we wait for the files on disk to settle before trying to load
     42 // them. This alleviates the problem of reading partially written files and
     43 // makes it possible to batch quasi-simultaneous changes.
     44 const int kSettleIntervalSeconds = 5;
     45 
     46 }  // namespace
     47 
     48 class PolicyWatcherLinux : public PolicyWatcher {
     49  public:
     50   PolicyWatcherLinux(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     51                      const base::FilePath& config_dir)
     52       : PolicyWatcher(task_runner),
     53         config_dir_(config_dir),
     54         weak_factory_(this) {
     55   }
     56 
     57   virtual ~PolicyWatcherLinux() {}
     58 
     59  protected:
     60   virtual void StartWatchingInternal() OVERRIDE {
     61     DCHECK(OnPolicyWatcherThread());
     62     watcher_.reset(new base::FilePathWatcher());
     63 
     64     if (!config_dir_.empty() &&
     65         !watcher_->Watch(
     66             config_dir_, false,
     67             base::Bind(&PolicyWatcherLinux::OnFilePathChanged,
     68                        weak_factory_.GetWeakPtr()))) {
     69       OnFilePathChanged(config_dir_, true);
     70     }
     71 
     72     // There might have been changes to the directory in the time between
     73     // construction of the loader and initialization of the watcher. Call reload
     74     // to detect if that is the case.
     75     Reload();
     76 
     77     ScheduleFallbackReloadTask();
     78   }
     79 
     80   virtual void StopWatchingInternal() OVERRIDE {
     81     DCHECK(OnPolicyWatcherThread());
     82 
     83     // Stop watching for changes to files in the policies directory.
     84     watcher_.reset();
     85 
     86     // Orphan any pending OnFilePathChanged tasks.
     87     weak_factory_.InvalidateWeakPtrs();
     88   }
     89 
     90  private:
     91   void OnFilePathChanged(const base::FilePath& path, bool error) {
     92     DCHECK(OnPolicyWatcherThread());
     93 
     94     if (!error)
     95       Reload();
     96     else
     97       LOG(ERROR) << "PolicyWatcherLinux on " << path.value() << " failed.";
     98   }
     99 
    100   base::Time GetLastModification() {
    101     DCHECK(OnPolicyWatcherThread());
    102     base::Time last_modification = base::Time();
    103     base::PlatformFileInfo file_info;
    104 
    105     // If the path does not exist or points to a directory, it's safe to load.
    106     if (!file_util::GetFileInfo(config_dir_, &file_info) ||
    107         !file_info.is_directory) {
    108       return last_modification;
    109     }
    110 
    111     // Enumerate the files and find the most recent modification timestamp.
    112     base::FileEnumerator file_enumerator(config_dir_,
    113                                          false,
    114                                          base::FileEnumerator::FILES);
    115     for (base::FilePath config_file = file_enumerator.Next();
    116          !config_file.empty();
    117          config_file = file_enumerator.Next()) {
    118       if (file_util::GetFileInfo(config_file, &file_info) &&
    119           !file_info.is_directory) {
    120         last_modification = std::max(last_modification,
    121                                      file_info.last_modified);
    122       }
    123     }
    124 
    125     return last_modification;
    126   }
    127 
    128   // Returns NULL if the policy dictionary couldn't be read.
    129   scoped_ptr<DictionaryValue> Load() {
    130     DCHECK(OnPolicyWatcherThread());
    131     // Enumerate the files and sort them lexicographically.
    132     std::set<base::FilePath> files;
    133     base::FileEnumerator file_enumerator(config_dir_, false,
    134                                          base::FileEnumerator::FILES);
    135     for (base::FilePath config_file_path = file_enumerator.Next();
    136          !config_file_path.empty(); config_file_path = file_enumerator.Next())
    137       files.insert(config_file_path);
    138 
    139     // Start with an empty dictionary and merge the files' contents.
    140     scoped_ptr<DictionaryValue> policy(new DictionaryValue());
    141     for (std::set<base::FilePath>::iterator config_file_iter = files.begin();
    142          config_file_iter != files.end(); ++config_file_iter) {
    143       JSONFileValueSerializer deserializer(*config_file_iter);
    144       deserializer.set_allow_trailing_comma(true);
    145       int error_code = 0;
    146       std::string error_msg;
    147       scoped_ptr<Value> value(
    148           deserializer.Deserialize(&error_code, &error_msg));
    149       if (!value.get()) {
    150         LOG(WARNING) << "Failed to read configuration file "
    151                      << config_file_iter->value() << ": " << error_msg;
    152         return scoped_ptr<DictionaryValue>();
    153       }
    154       if (!value->IsType(Value::TYPE_DICTIONARY)) {
    155         LOG(WARNING) << "Expected JSON dictionary in configuration file "
    156                      << config_file_iter->value();
    157         return scoped_ptr<DictionaryValue>();
    158       }
    159       policy->MergeDictionary(static_cast<DictionaryValue*>(value.get()));
    160     }
    161 
    162     return policy.Pass();
    163   }
    164 
    165   virtual void Reload() OVERRIDE {
    166     DCHECK(OnPolicyWatcherThread());
    167     // Check the directory time in order to see whether a reload is required.
    168     base::TimeDelta delay;
    169     base::Time now = base::Time::Now();
    170     if (!IsSafeToReloadPolicy(now, &delay)) {
    171       ScheduleReloadTask(delay);
    172       return;
    173     }
    174 
    175     // Check again in case the directory has changed while reading it.
    176     if (!IsSafeToReloadPolicy(now, &delay)) {
    177       ScheduleReloadTask(delay);
    178       return;
    179     }
    180 
    181     // Load the policy definitions.
    182     scoped_ptr<DictionaryValue> new_policy = Load();
    183     if (new_policy.get()) {
    184       UpdatePolicies(new_policy.get());
    185       ScheduleFallbackReloadTask();
    186     } else {
    187       // A failure to load policy definitions is probably temporary, so try
    188       // again soon.
    189       ScheduleReloadTask(base::TimeDelta::FromSeconds(kSettleIntervalSeconds));
    190     }
    191   }
    192 
    193   bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) {
    194     DCHECK(OnPolicyWatcherThread());
    195     DCHECK(delay);
    196     const base::TimeDelta kSettleInterval =
    197         base::TimeDelta::FromSeconds(kSettleIntervalSeconds);
    198 
    199     base::Time last_modification = GetLastModification();
    200     if (last_modification.is_null())
    201       return true;
    202 
    203     if (last_modification_file_.is_null())
    204       last_modification_file_ = last_modification;
    205 
    206     // If there was a change since the last recorded modification, wait some
    207     // more.
    208     if (last_modification != last_modification_file_) {
    209       last_modification_file_ = last_modification;
    210       last_modification_clock_ = now;
    211       *delay = kSettleInterval;
    212       return false;
    213     }
    214 
    215     // Check whether the settle interval has elapsed.
    216     base::TimeDelta age = now - last_modification_clock_;
    217     if (age < kSettleInterval) {
    218       *delay = kSettleInterval - age;
    219       return false;
    220     }
    221 
    222     return true;
    223   }
    224 
    225   // Managed with a scoped_ptr rather than being declared as an inline member to
    226   // decouple the watcher's life cycle from the PolicyWatcherLinux. This
    227   // decoupling makes it possible to destroy the watcher before the loader's
    228   // destructor is called (e.g. during Stop), since |watcher_| internally holds
    229   // a reference to the loader and keeps it alive.
    230   scoped_ptr<base::FilePathWatcher> watcher_;
    231 
    232   // Records last known modification timestamp of |config_dir_|.
    233   base::Time last_modification_file_;
    234 
    235   // The wall clock time at which the last modification timestamp was
    236   // recorded.  It's better to not assume the file notification time and the
    237   // wall clock times come from the same source, just in case there is some
    238   // non-local filesystem involved.
    239   base::Time last_modification_clock_;
    240 
    241   const base::FilePath config_dir_;
    242 
    243   // Allows us to cancel any inflight FileWatcher events or scheduled reloads.
    244   base::WeakPtrFactory<PolicyWatcherLinux> weak_factory_;
    245 };
    246 
    247 PolicyWatcher* PolicyWatcher::Create(
    248     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
    249   base::FilePath policy_dir(kPolicyDir);
    250   return new PolicyWatcherLinux(task_runner, policy_dir);
    251 }
    252 
    253 }  // namespace policy_hack
    254 }  // namespace remoting
    255