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