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