Home | History | Annotate | Download | only in cloud
      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 
     21 namespace em = enterprise_management;
     22 
     23 namespace policy {
     24 
     25 namespace {
     26 
     27 const char kValue[] = "Value";
     28 const char kLevel[] = "Level";
     29 const char kRecommended[] = "Recommended";
     30 
     31 const 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 
     45 const 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 
     53 const 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 
     63 ComponentCloudPolicyStore::Delegate::~Delegate() {}
     64 
     65 ComponentCloudPolicyStore::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 
     75 ComponentCloudPolicyStore::~ComponentCloudPolicyStore() {
     76   DCHECK(CalledOnValidThread());
     77 }
     78 
     79 // static
     80 bool ComponentCloudPolicyStore::SupportsDomain(PolicyDomain domain) {
     81   return GetDomainConstants(domain) != NULL;
     82 }
     83 
     84 // static
     85 bool 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
     94 bool 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 
    102 const 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 
    110 void 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 
    119 void 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 
    158 bool 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 
    179 void 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 
    194 void 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 
    234 void 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 
    247 bool 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 
    270 bool 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 
    316 bool 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 
    324 bool 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