1// Copyright (c) 2013 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/cloud/component_cloud_policy_store.h"
6
7#include "base/callback.h"
8#include "base/json/json_reader.h"
9#include "base/logging.h"
10#include "base/strings/string_util.h"
11#include "base/values.h"
12#include "components/policy/core/common/cloud/cloud_policy_constants.h"
13#include "components/policy/core/common/cloud/cloud_policy_validator.h"
14#include "components/policy/core/common/external_data_fetcher.h"
15#include "components/policy/core/common/policy_map.h"
16#include "crypto/sha2.h"
17#include "policy/proto/chrome_extension_policy.pb.h"
18#include "policy/proto/device_management_backend.pb.h"
19#include "url/gurl.h"
20
21namespace em = enterprise_management;
22
23namespace policy {
24
25namespace {
26
27const char kValue[] = "Value";
28const char kLevel[] = "Level";
29const char kRecommended[] = "Recommended";
30
31const struct DomainConstants {
32  PolicyDomain domain;
33  const char* proto_cache_key;
34  const char* data_cache_key;
35  const char* policy_type;
36} kDomains[] = {
37  {
38    POLICY_DOMAIN_EXTENSIONS,
39    "extension-policy",
40    "extension-policy-data",
41    dm_protocol::kChromeExtensionPolicyType,
42  },
43};
44
45const DomainConstants* GetDomainConstants(PolicyDomain domain) {
46  for (size_t i = 0; i < arraysize(kDomains); ++i) {
47    if (kDomains[i].domain == domain)
48      return &kDomains[i];
49  }
50  return NULL;
51}
52
53const DomainConstants* GetDomainConstantsForType(const std::string& type) {
54  for (size_t i = 0; i < arraysize(kDomains); ++i) {
55    if (kDomains[i].policy_type == type)
56      return &kDomains[i];
57  }
58  return NULL;
59}
60
61}  // namespace
62
63ComponentCloudPolicyStore::Delegate::~Delegate() {}
64
65ComponentCloudPolicyStore::ComponentCloudPolicyStore(
66    Delegate* delegate,
67    ResourceCache* cache)
68    : delegate_(delegate),
69      cache_(cache) {
70  // Allow the store to be created on a different thread than the thread that
71  // will end up using it.
72  DetachFromThread();
73}
74
75ComponentCloudPolicyStore::~ComponentCloudPolicyStore() {
76  DCHECK(CalledOnValidThread());
77}
78
79// static
80bool ComponentCloudPolicyStore::SupportsDomain(PolicyDomain domain) {
81  return GetDomainConstants(domain) != NULL;
82}
83
84// static
85bool ComponentCloudPolicyStore::GetPolicyType(PolicyDomain domain,
86                                              std::string* policy_type) {
87  const DomainConstants* constants = GetDomainConstants(domain);
88  if (constants)
89    *policy_type = constants->policy_type;
90  return constants != NULL;
91}
92
93// static
94bool ComponentCloudPolicyStore::GetPolicyDomain(const std::string& policy_type,
95                                                PolicyDomain* domain) {
96  const DomainConstants* constants = GetDomainConstantsForType(policy_type);
97  if (constants)
98    *domain = constants->domain;
99  return constants != NULL;
100}
101
102const std::string& ComponentCloudPolicyStore::GetCachedHash(
103    const PolicyNamespace& ns) const {
104  DCHECK(CalledOnValidThread());
105  std::map<PolicyNamespace, std::string>::const_iterator it =
106      cached_hashes_.find(ns);
107  return it == cached_hashes_.end() ? base::EmptyString() : it->second;
108}
109
110void ComponentCloudPolicyStore::SetCredentials(const std::string& username,
111                                               const std::string& dm_token) {
112  DCHECK(CalledOnValidThread());
113  DCHECK(username_.empty() || username == username_);
114  DCHECK(dm_token_.empty() || dm_token == dm_token_);
115  username_ = username;
116  dm_token_ = dm_token;
117}
118
119void ComponentCloudPolicyStore::Load() {
120  DCHECK(CalledOnValidThread());
121  typedef std::map<std::string, std::string> ContentMap;
122
123  // Load all cached policy protobufs for each domain.
124  for (size_t domain = 0; domain < arraysize(kDomains); ++domain) {
125    const DomainConstants& constants = kDomains[domain];
126    ContentMap protos;
127    cache_->LoadAllSubkeys(constants.proto_cache_key, &protos);
128    for (ContentMap::iterator it = protos.begin(); it != protos.end(); ++it) {
129      const std::string& id(it->first);
130      PolicyNamespace ns(constants.domain, id);
131
132      // Validate each protobuf.
133      scoped_ptr<em::PolicyFetchResponse> proto(new em::PolicyFetchResponse);
134      em::ExternalPolicyData payload;
135      if (!proto->ParseFromString(it->second) ||
136          !ValidateProto(
137              proto.Pass(), constants.policy_type, id, &payload, NULL)) {
138        Delete(ns);
139        continue;
140      }
141
142      // The protobuf looks good; load the policy data.
143      std::string data;
144      PolicyMap policy;
145      if (cache_->Load(constants.data_cache_key, id, &data) &&
146          ValidateData(data, payload.secure_hash(), &policy)) {
147        // The data is also good; expose the policies.
148        policy_bundle_.Get(ns).Swap(&policy);
149        cached_hashes_[ns] = payload.secure_hash();
150      } else {
151        // The data for this proto couldn't be loaded or is corrupted.
152        Delete(ns);
153      }
154    }
155  }
156}
157
158bool ComponentCloudPolicyStore::Store(const PolicyNamespace& ns,
159                                      const std::string& serialized_policy,
160                                      const std::string& secure_hash,
161                                      const std::string& data) {
162  DCHECK(CalledOnValidThread());
163  const DomainConstants* constants = GetDomainConstants(ns.domain);
164  PolicyMap policy;
165  // |serialized_policy| has already been validated; validate the data now.
166  if (!constants || !ValidateData(data, secure_hash, &policy))
167    return false;
168
169  // Flush the proto and the data to the cache.
170  cache_->Store(constants->proto_cache_key, ns.component_id, serialized_policy);
171  cache_->Store(constants->data_cache_key, ns.component_id, data);
172  // And expose the policy.
173  policy_bundle_.Get(ns).Swap(&policy);
174  cached_hashes_[ns] = secure_hash;
175  delegate_->OnComponentCloudPolicyStoreUpdated();
176  return true;
177}
178
179void ComponentCloudPolicyStore::Delete(const PolicyNamespace& ns) {
180  DCHECK(CalledOnValidThread());
181  const DomainConstants* constants = GetDomainConstants(ns.domain);
182  if (!constants)
183    return;
184
185  cache_->Delete(constants->proto_cache_key, ns.component_id);
186  cache_->Delete(constants->data_cache_key, ns.component_id);
187
188  if (!policy_bundle_.Get(ns).empty()) {
189    policy_bundle_.Get(ns).Clear();
190    delegate_->OnComponentCloudPolicyStoreUpdated();
191  }
192}
193
194void ComponentCloudPolicyStore::Purge(
195    PolicyDomain domain,
196    const ResourceCache::SubkeyFilter& filter) {
197  DCHECK(CalledOnValidThread());
198  const DomainConstants* constants = GetDomainConstants(domain);
199  if (!constants)
200    return;
201
202  cache_->FilterSubkeys(constants->proto_cache_key, filter);
203  cache_->FilterSubkeys(constants->data_cache_key, filter);
204
205  // Stop serving policies for purged namespaces.
206  bool purged_current_policies = false;
207  for (PolicyBundle::const_iterator it = policy_bundle_.begin();
208       it != policy_bundle_.end(); ++it) {
209    if (it->first.domain == domain &&
210        filter.Run(it->first.component_id) &&
211        !policy_bundle_.Get(it->first).empty()) {
212      policy_bundle_.Get(it->first).Clear();
213      purged_current_policies = true;
214    }
215  }
216
217  // Purge cached hashes, so that those namespaces can be fetched again if the
218  // policy state changes.
219  std::map<PolicyNamespace, std::string>::iterator it = cached_hashes_.begin();
220  while (it != cached_hashes_.end()) {
221    if (it->first.domain == domain && filter.Run(it->first.component_id)) {
222      std::map<PolicyNamespace, std::string>::iterator prev = it;
223      ++it;
224      cached_hashes_.erase(prev);
225    } else {
226      ++it;
227    }
228  }
229
230  if (purged_current_policies)
231    delegate_->OnComponentCloudPolicyStoreUpdated();
232}
233
234void ComponentCloudPolicyStore::Clear() {
235  for (size_t i = 0; i < arraysize(kDomains); ++i) {
236    cache_->Clear(kDomains[i].proto_cache_key);
237    cache_->Clear(kDomains[i].data_cache_key);
238  }
239  cached_hashes_.clear();
240  const PolicyBundle empty_bundle;
241  if (!policy_bundle_.Equals(empty_bundle)) {
242    policy_bundle_.Clear();
243    delegate_->OnComponentCloudPolicyStoreUpdated();
244  }
245}
246
247bool ComponentCloudPolicyStore::ValidatePolicy(
248    scoped_ptr<em::PolicyFetchResponse> proto,
249    PolicyNamespace* ns,
250    em::ExternalPolicyData* payload) {
251  em::PolicyData policy_data;
252  if (!ValidateProto(
253          proto.Pass(), std::string(), std::string(), payload, &policy_data)) {
254    return false;
255  }
256
257  if (!policy_data.has_policy_type())
258    return false;
259
260  const DomainConstants* constants =
261      GetDomainConstantsForType(policy_data.policy_type());
262  if (!constants || !policy_data.has_settings_entity_id())
263    return false;
264
265  ns->domain = constants->domain;
266  ns->component_id = policy_data.settings_entity_id();
267  return true;
268}
269
270bool ComponentCloudPolicyStore::ValidateProto(
271    scoped_ptr<em::PolicyFetchResponse> proto,
272    const std::string& policy_type,
273    const std::string& settings_entity_id,
274    em::ExternalPolicyData* payload,
275    em::PolicyData* policy_data) {
276  if (username_.empty() || dm_token_.empty())
277    return false;
278
279  scoped_ptr<ComponentCloudPolicyValidator> validator(
280      ComponentCloudPolicyValidator::Create(
281          proto.Pass(), scoped_refptr<base::SequencedTaskRunner>()));
282  validator->ValidateUsername(username_, true);
283  validator->ValidateDMToken(dm_token_,
284                             ComponentCloudPolicyValidator::DM_TOKEN_REQUIRED);
285  if (!policy_type.empty())
286    validator->ValidatePolicyType(policy_type);
287  if (!settings_entity_id.empty())
288    validator->ValidateSettingsEntityId(settings_entity_id);
289  validator->ValidatePayload();
290  // TODO(joaodasilva): validate signature.
291  validator->RunValidation();
292  if (!validator->success())
293    return false;
294
295  em::ExternalPolicyData* data = validator->payload().get();
296  // The download URL must be empty, or must be a valid URL.
297  // An empty download URL signals that this component doesn't have cloud
298  // policy, or that the policy has been removed.
299  if (data->has_download_url() && !data->download_url().empty()) {
300    if (!GURL(data->download_url()).is_valid() ||
301        !data->has_secure_hash() ||
302        data->secure_hash().empty()) {
303      return false;
304    }
305  } else if (data->has_secure_hash()) {
306    return false;
307  }
308
309  if (payload)
310    payload->Swap(validator->payload().get());
311  if (policy_data)
312    policy_data->Swap(validator->policy_data().get());
313  return true;
314}
315
316bool ComponentCloudPolicyStore::ValidateData(
317    const std::string& data,
318    const std::string& secure_hash,
319    PolicyMap* policy) {
320  return crypto::SHA256HashString(data) == secure_hash &&
321      ParsePolicy(data, policy);
322}
323
324bool ComponentCloudPolicyStore::ParsePolicy(const std::string& data,
325                                            PolicyMap* policy) {
326  scoped_ptr<base::Value> json(base::JSONReader::Read(
327      data, base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN));
328  base::DictionaryValue* dict = NULL;
329  if (!json || !json->GetAsDictionary(&dict))
330    return false;
331
332  // Each top-level key maps a policy name to its description.
333  //
334  // Each description is an object that contains the policy value under the
335  // "Value" key. The optional "Level" key is either "Mandatory" (default) or
336  // "Recommended".
337  for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
338    base::DictionaryValue* description = NULL;
339    if (!dict->GetDictionaryWithoutPathExpansion(it.key(), &description))
340      return false;
341
342    scoped_ptr<base::Value> value;
343    if (!description->RemoveWithoutPathExpansion(kValue, &value))
344      return false;
345
346    PolicyLevel level = POLICY_LEVEL_MANDATORY;
347    std::string level_string;
348    if (description->GetStringWithoutPathExpansion(kLevel, &level_string) &&
349        level_string == kRecommended) {
350      level = POLICY_LEVEL_RECOMMENDED;
351    }
352
353    // If policy for components is ever used for device-level settings then
354    // this must support a configurable scope; assuming POLICY_SCOPE_USER is
355    // fine for now.
356    policy->Set(it.key(), level, POLICY_SCOPE_USER, value.release(), NULL);
357  }
358
359  return true;
360}
361
362}  // namespace policy
363