1 // Copyright 2018 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 "chrome/browser/chromeos/policy/component_active_directory_policy_service.h"
6
7 #include <iterator>
8
9 #include "base/bind.h"
10 #include "base/json/json_reader.h"
11 #include "base/logging.h"
12 #include "base/optional.h"
13 #include "base/strings/string_util.h"
14 #include "base/values.h"
15 #include "chromeos/dbus/login_manager/policy_descriptor.pb.h"
16 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
17 #include "components/policy/core/common/policy_bundle.h"
18 #include "components/policy/core/common/policy_map.h"
19 #include "components/policy/core/common/registry_dict.h"
20 #include "components/policy/proto/device_management_backend.pb.h"
21
22 namespace em = enterprise_management;
23
24 namespace policy {
25
26 using RetrieveResult = ComponentActiveDirectoryPolicyRetriever::RetrieveResult;
27 using RetrieveResponseType =
28 ComponentActiveDirectoryPolicyRetriever::ResponseType;
29
30 namespace {
31
32 constexpr char kKeyMandatory[] = "Policy";
33 constexpr char kKeyRecommended[] = "Recommended";
34
35 // Policy level and corresponding path.
36 struct Level {
37 PolicyLevel level;
38 const char* json_key;
39 };
40
41 constexpr Level kLevels[] = {
42 {POLICY_LEVEL_MANDATORY, kKeyMandatory},
43 {POLICY_LEVEL_RECOMMENDED, kKeyRecommended},
44 };
45
46 // Returns the level out of |kLevels| with matching |json_key|. Uses
47 // case-insensitive comparison because |json_key| usually comes from Windows
48 // registry, which also uses case-insensitive comparison. Returns nullptr if
49 // no matching level is found.
FindMatchingLevel(const std::string & json_key)50 const Level* FindMatchingLevel(const std::string& json_key) {
51 for (const Level& level : kLevels) {
52 if (base::EqualsCaseInsensitiveASCII(json_key, level.json_key))
53 return &level;
54 }
55 return nullptr;
56 }
57
58 // Gets the policy_value() from the em::PolicyData nested in a serialized
59 // em::PolicyFetchResponse blob. Returns an empty string on error.
GetPolicyValue(const std::string & policy_fetch_response_blob)60 std::string GetPolicyValue(const std::string& policy_fetch_response_blob) {
61 em::PolicyFetchResponse policy_fetch_response;
62 em::PolicyData policy_data;
63 if (!policy_fetch_response.ParseFromString(policy_fetch_response_blob) ||
64 policy_fetch_response.policy_data().empty() ||
65 !policy_data.ParseFromString(policy_fetch_response.policy_data()) ||
66 policy_data.policy_value().empty()) {
67 LOG(ERROR) << "Could not parse fetch response";
68 return std::string();
69 }
70 return policy_data.policy_value();
71 }
72
73 // Parses |json| to a base::Value. Returns nullptr and prints errors
74 // on failure.
ParseJsonToDict(const std::string & json)75 base::Optional<base::Value> ParseJsonToDict(const std::string& json) {
76 base::JSONReader::ValueWithError value_with_error =
77 base::JSONReader::ReadAndReturnValueWithError(
78 json, base::JSON_ALLOW_TRAILING_COMMAS);
79 if (!value_with_error.value) {
80 LOG(ERROR) << "Could not parse policy value as JSON: "
81 << value_with_error.error_message;
82 return base::nullopt;
83 }
84
85 base::Value value = std::move(value_with_error.value.value());
86 if (!value.is_dict()) {
87 LOG(ERROR) << "The JSON policy value is not a dictionary.";
88 return base::nullopt;
89 }
90
91 return value;
92 }
93
94 // Gets the policy value from the |policy_fetch_response_blob|, parses it as
95 // JSON, uses |schema| to convert some values (e.g. 0/1 registry ints to bools)
96 // and puts it into |policy| for the given |scope|.
ParsePolicy(const std::string & policy_fetch_response_blob,const Schema & schema,PolicyScope scope,PolicyMap * policy)97 bool ParsePolicy(const std::string& policy_fetch_response_blob,
98 const Schema& schema,
99 PolicyScope scope,
100 PolicyMap* policy) {
101 // Get the policy value from the fetch response.
102 std::string policy_value = GetPolicyValue(policy_fetch_response_blob);
103 if (policy_value.empty())
104 return false;
105
106 // Parse the policy value, which should be a JSON string. The JSON is expected
107 // to be formatted like
108 // { "Policy":{ "Name1":Value1 }, "Recommended":{ "Name2":Value2 } }
109 // and matches the format on Chrome for Windows (see Load3rdPartyPolicy in
110 // PolicyLoaderWin). On a side note, this is different from the schema sent
111 // down from DMServer for Google cloud managed devices, which is
112 // { "Name1": { "Value":Value1 },
113 // "Name2": { "Value":Value2, "Level":"Recommended" } }
114 // (see ParsePolicy in ComponentCloudPolicyStore).
115 base::Optional<base::Value> dict = ParseJsonToDict(policy_value);
116
117 if (!dict.has_value())
118 return false;
119
120 // Search for "Policy" and "Recommended" keys in dict, perform some type
121 // conversions on the sub-dicts and put them into |policy|.
122 for (const auto& it : dict.value().DictItems()) {
123 const Level* level = FindMatchingLevel(it.first);
124 if (!level) {
125 LOG(WARNING) << "Unknown key '" << it.first
126 << "'. Expected 'Policy' or 'Recommended'.";
127 continue;
128 }
129
130 // Type-convert policy values using schema information. Since GPO and
131 // registry don't support certain types, they use other types that have to
132 // be converted to the types specified in the schema:
133 // string -> double for 'number' type policies
134 // int -> bool for 'boolean' type policies
135 base::Optional<base::Value> converted_value =
136 ConvertRegistryValue(it.second, schema);
137 if (!converted_value.has_value() || !converted_value.value().is_dict()) {
138 LOG(ERROR) << "Failed to filter JSON policy at level " << level->json_key;
139 continue;
140 }
141 const base::DictionaryValue& converted_dict =
142 base::Value::AsDictionaryValue(converted_value.value());
143
144 // Put the policy into the right spot.
145 policy->LoadFrom(&converted_dict, level->level, scope,
146 POLICY_SOURCE_ACTIVE_DIRECTORY);
147 }
148 return true;
149 }
150
151 } // namespace
152
~Delegate()153 ComponentActiveDirectoryPolicyService::Delegate::~Delegate() {}
154
ComponentActiveDirectoryPolicyService(PolicyScope scope,PolicyDomain domain,login_manager::PolicyAccountType account_type,const std::string & account_id,Delegate * delegate,SchemaRegistry * schema_registry)155 ComponentActiveDirectoryPolicyService::ComponentActiveDirectoryPolicyService(
156 PolicyScope scope,
157 PolicyDomain domain,
158 login_manager::PolicyAccountType account_type,
159 const std::string& account_id,
160 Delegate* delegate,
161 SchemaRegistry* schema_registry)
162 : scope_(scope),
163 domain_(domain),
164 account_type_(account_type),
165 account_id_(account_id),
166 delegate_(delegate),
167 schema_registry_(schema_registry) {
168 // Observe the schema registry for keeping |current_schema_map_| up to date.
169 schema_registry_->AddObserver(this);
170 UpdateFromSchemaRegistry();
171 }
172
173 ComponentActiveDirectoryPolicyService::
~ComponentActiveDirectoryPolicyService()174 ~ComponentActiveDirectoryPolicyService() {
175 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
176
177 schema_registry_->RemoveObserver(this);
178 }
179
RetrievePolicies()180 void ComponentActiveDirectoryPolicyService::RetrievePolicies() {
181 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
182
183 // Ignore if we're not ready yet. Note that UpdateFromSchemaRegistry() calls
184 // RetrievePolicies() as soon as the schemas are ready.
185 if (!current_schema_map_)
186 return;
187
188 // If there's an in-flight retrieval request, don't cancel it, but call
189 // RetrievePolicies() again once it finishes. Otherwise, requests might never
190 // finish if new requests are scheduled in quick succession.
191 if (policy_retriever_) {
192 should_retrieve_again_ = true;
193 return;
194 }
195
196 // Get all namespaces from the schema map that match our |domain_|.
197 std::vector<PolicyNamespace> namespaces;
198 for (const auto& domain_kv : current_schema_map_->GetDomains()) {
199 if (domain_ == domain_kv.first) {
200 const ComponentMap& component_map = domain_kv.second;
201 for (const auto& component_kv : component_map)
202 namespaces.emplace_back(domain_, component_kv.first /* component_id */);
203 }
204 }
205
206 // Retrieve the corresponding policies from Session Manager.
207 DVLOG(1) << "Retrieving policies for " << namespaces.size() << " namespaces";
208 policy_retriever_ = std::make_unique<ComponentActiveDirectoryPolicyRetriever>(
209 account_type_, account_id_, std::move(namespaces),
210 base::BindOnce(&ComponentActiveDirectoryPolicyService::OnPolicyRetrieved,
211 weak_ptr_factory_.GetWeakPtr()));
212 policy_retriever_->Start();
213 }
214
OnSchemaRegistryReady()215 void ComponentActiveDirectoryPolicyService::OnSchemaRegistryReady() {
216 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
217 UpdateFromSchemaRegistry();
218 }
219
OnSchemaRegistryUpdated(bool has_new_schemas)220 void ComponentActiveDirectoryPolicyService::OnSchemaRegistryUpdated(
221 bool has_new_schemas) {
222 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
223 UpdateFromSchemaRegistry();
224 }
225
UpdateFromSchemaRegistry()226 void ComponentActiveDirectoryPolicyService::UpdateFromSchemaRegistry() {
227 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
228 // Ignore if registry is not ready yet.
229 if (!schema_registry_->IsReady())
230 return;
231
232 DVLOG(1) << "Updating schema map";
233 current_schema_map_ = schema_registry_->schema_map();
234
235 RetrievePolicies();
236 }
237
OnPolicyRetrieved(std::vector<RetrieveResult> results)238 void ComponentActiveDirectoryPolicyService::OnPolicyRetrieved(
239 std::vector<RetrieveResult> results) {
240 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
241
242 // Convert the list of JSON policy strings to a PolicyBundle.
243 auto policy = std::make_unique<PolicyBundle>();
244 for (const RetrieveResult& result : results) {
245 if (result.response != RetrieveResponseType::SUCCESS)
246 continue;
247
248 // Always create an entry for the ns, even if policy is empty or invalid.
249 PolicyMap& policy_map = policy->Get(result.ns);
250
251 // Ignore empty policy (SessionManager returns empty policy if the policy
252 // requested does not exist, e.g. extension with no policy set).
253 if (result.policy_fetch_response_blob.empty())
254 continue;
255
256 // Get schema. It's possible that we've requested policy for a namespace
257 // that's not in the schema registry anymore, e.g. if the schema map
258 // changed while the request was in flight. Just silently ignore.
259 const Schema* schema = current_schema_map_->GetSchema(result.ns);
260 if (!schema) {
261 DVLOG(1) << "No schema for component id " << result.ns.component_id;
262 continue;
263 }
264
265 // Parse JSON policy, do some type conversions and store policy.
266 if (!ParsePolicy(result.policy_fetch_response_blob, *schema, scope_,
267 &policy_map)) {
268 LOG(ERROR) << "Failed to parse policy for component id "
269 << result.ns.component_id;
270 policy_map.Clear();
271 }
272 }
273
274 // Filter policy by the corresponding schema and send it to the delegate.
275 FilterAndInstallPolicy(std::move(policy));
276
277 // Reset retrieval request. If RetrievePolicies() was called while the current
278 // retrieval requests was in-flight, queue it again here.
279 policy_retriever_.reset();
280 if (should_retrieve_again_) {
281 should_retrieve_again_ = false;
282 DCHECK(!policy_retriever_);
283 RetrievePolicies();
284 }
285 }
286
FilterAndInstallPolicy(std::unique_ptr<PolicyBundle> policy)287 void ComponentActiveDirectoryPolicyService::FilterAndInstallPolicy(
288 std::unique_ptr<PolicyBundle> policy) {
289 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
290 policy_ = std::move(policy);
291
292 // Remove policies that don't match the schema.
293 current_schema_map_->FilterBundle(policy_.get(),
294 /*drop_invalid_component_policies=*/true);
295
296 DVLOG(1) << "Installed policy (count = "
297 << std::distance(policy_->begin(), policy_->end()) << ")";
298 delegate_->OnComponentActiveDirectoryPolicyUpdated();
299 }
300
301 } // namespace policy
302