xref: /aosp_15_r20/external/libchrome/components/policy/core/common/config_dir_policy_loader.cc (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
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