Home | History | Annotate | Download | only in permissions
      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 "chrome/common/extensions/permissions/permission_set.h"
      6 
      7 #include <algorithm>
      8 #include <iterator>
      9 #include <string>
     10 
     11 #include "base/stl_util.h"
     12 #include "chrome/common/extensions/permissions/chrome_scheme_hosts.h"
     13 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
     14 #include "chrome/common/extensions/permissions/permissions_info.h"
     15 #include "content/public/common/url_constants.h"
     16 #include "extensions/common/url_pattern.h"
     17 #include "extensions/common/url_pattern_set.h"
     18 #include "grit/generated_resources.h"
     19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
     20 #include "ui/base/l10n/l10n_util.h"
     21 #include "url/gurl.h"
     22 
     23 using extensions::URLPatternSet;
     24 
     25 namespace {
     26 
     27 // Helper for GetDistinctHosts(): com > net > org > everything else.
     28 bool RcdBetterThan(const std::string& a, const std::string& b) {
     29   if (a == b)
     30     return false;
     31   if (a == "com")
     32     return true;
     33   if (a == "net")
     34     return b != "com";
     35   if (a == "org")
     36     return b != "com" && b != "net";
     37   return false;
     38 }
     39 
     40 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
     41   DCHECK(out);
     42   for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
     43     URLPattern p = *i;
     44     p.SetPath("/*");
     45     out->AddPattern(p);
     46   }
     47 }
     48 
     49 }  // namespace
     50 
     51 namespace extensions {
     52 
     53 //
     54 // PermissionSet
     55 //
     56 
     57 PermissionSet::PermissionSet() {}
     58 
     59 PermissionSet::PermissionSet(
     60     const APIPermissionSet& apis,
     61     const URLPatternSet& explicit_hosts,
     62     const URLPatternSet& scriptable_hosts)
     63     : apis_(apis),
     64       scriptable_hosts_(scriptable_hosts) {
     65   AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
     66   InitImplicitPermissions();
     67   InitEffectiveHosts();
     68 }
     69 
     70 // static
     71 PermissionSet* PermissionSet::CreateDifference(
     72     const PermissionSet* set1,
     73     const PermissionSet* set2) {
     74   scoped_refptr<PermissionSet> empty = new PermissionSet();
     75   const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
     76   const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
     77 
     78   APIPermissionSet apis;
     79   APIPermissionSet::Difference(set1_safe->apis(), set2_safe->apis(), &apis);
     80 
     81   URLPatternSet explicit_hosts;
     82   URLPatternSet::CreateDifference(set1_safe->explicit_hosts(),
     83                                   set2_safe->explicit_hosts(),
     84                                   &explicit_hosts);
     85 
     86   URLPatternSet scriptable_hosts;
     87   URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
     88                                   set2_safe->scriptable_hosts(),
     89                                   &scriptable_hosts);
     90 
     91   return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
     92 }
     93 
     94 // static
     95 PermissionSet* PermissionSet::CreateIntersection(
     96     const PermissionSet* set1,
     97     const PermissionSet* set2) {
     98   scoped_refptr<PermissionSet> empty = new PermissionSet();
     99   const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
    100   const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
    101 
    102   APIPermissionSet apis;
    103   APIPermissionSet::Intersection(set1_safe->apis(), set2_safe->apis(), &apis);
    104 
    105   URLPatternSet explicit_hosts;
    106   URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(),
    107                                     set2_safe->explicit_hosts(),
    108                                     &explicit_hosts);
    109 
    110   URLPatternSet scriptable_hosts;
    111   URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
    112                                     set2_safe->scriptable_hosts(),
    113                                     &scriptable_hosts);
    114 
    115   return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
    116 }
    117 
    118 // static
    119 PermissionSet* PermissionSet::CreateUnion(
    120     const PermissionSet* set1,
    121     const PermissionSet* set2) {
    122   scoped_refptr<PermissionSet> empty = new PermissionSet();
    123   const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
    124   const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
    125 
    126   APIPermissionSet apis;
    127   APIPermissionSet::Union(set1_safe->apis(), set2_safe->apis(), &apis);
    128 
    129   URLPatternSet explicit_hosts;
    130   URLPatternSet::CreateUnion(set1_safe->explicit_hosts(),
    131                              set2_safe->explicit_hosts(),
    132                              &explicit_hosts);
    133 
    134   URLPatternSet scriptable_hosts;
    135   URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
    136                              set2_safe->scriptable_hosts(),
    137                              &scriptable_hosts);
    138 
    139   return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
    140 }
    141 
    142 // static
    143 PermissionSet* PermissionSet::ExcludeNotInManifestPermissions(
    144     const PermissionSet* set) {
    145   if (!set)
    146     return new PermissionSet();
    147 
    148   APIPermissionSet apis;
    149   for (APIPermissionSet::const_iterator i = set->apis().begin();
    150        i != set->apis().end(); ++i) {
    151     if (!i->ManifestEntryForbidden())
    152       apis.insert(i->Clone());
    153   }
    154 
    155   return new PermissionSet(
    156       apis, set->explicit_hosts(), set->scriptable_hosts());
    157 }
    158 
    159 bool PermissionSet::operator==(
    160     const PermissionSet& rhs) const {
    161   return apis_ == rhs.apis_ &&
    162       scriptable_hosts_ == rhs.scriptable_hosts_ &&
    163       explicit_hosts_ == rhs.explicit_hosts_;
    164 }
    165 
    166 bool PermissionSet::Contains(const PermissionSet& set) const {
    167   return apis_.Contains(set.apis()) &&
    168          explicit_hosts().Contains(set.explicit_hosts()) &&
    169          scriptable_hosts().Contains(set.scriptable_hosts());
    170 }
    171 
    172 std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
    173   std::set<std::string> apis_str;
    174   for (APIPermissionSet::const_iterator i = apis_.begin();
    175        i != apis_.end(); ++i) {
    176     apis_str.insert(i->name());
    177   }
    178   return apis_str;
    179 }
    180 
    181 std::set<std::string> PermissionSet::GetDistinctHostsForDisplay() const {
    182   URLPatternSet hosts_displayed_as_url;
    183   // Filters out every URL pattern that matches chrome:// scheme.
    184   for (URLPatternSet::const_iterator i = effective_hosts_.begin();
    185        i != effective_hosts_.end(); ++i) {
    186     if (i->scheme() != chrome::kChromeUIScheme) {
    187       hosts_displayed_as_url.AddPattern(*i);
    188     }
    189   }
    190   return GetDistinctHosts(hosts_displayed_as_url, true, true);
    191 }
    192 
    193 PermissionMessages PermissionSet::GetPermissionMessages(
    194     Manifest::Type extension_type) const {
    195   PermissionMessages messages;
    196 
    197   if (HasEffectiveFullAccess()) {
    198     messages.push_back(PermissionMessage(
    199         PermissionMessage::kFullAccess,
    200         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
    201     return messages;
    202   }
    203 
    204   std::set<PermissionMessage> host_msgs =
    205       GetHostPermissionMessages(extension_type);
    206   std::set<PermissionMessage> api_msgs = GetAPIPermissionMessages();
    207   messages.insert(messages.end(), host_msgs.begin(), host_msgs.end());
    208   messages.insert(messages.end(), api_msgs.begin(), api_msgs.end());
    209 
    210   return messages;
    211 }
    212 
    213 std::vector<string16> PermissionSet::GetWarningMessages(
    214     Manifest::Type extension_type) const {
    215   std::vector<string16> messages;
    216   PermissionMessages permissions = GetPermissionMessages(extension_type);
    217 
    218   bool audio_capture = false;
    219   bool video_capture = false;
    220   bool media_galleries_read = false;
    221   bool media_galleries_copy_to = false;
    222   for (PermissionMessages::const_iterator i = permissions.begin();
    223        i != permissions.end(); ++i) {
    224     switch (i->id()) {
    225       case PermissionMessage::kAudioCapture:
    226         audio_capture = true;
    227         break;
    228       case PermissionMessage::kVideoCapture:
    229         video_capture = true;
    230         break;
    231       case PermissionMessage::kMediaGalleriesAllGalleriesRead:
    232         media_galleries_read = true;
    233         break;
    234       case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo:
    235         media_galleries_copy_to = true;
    236         break;
    237       default:
    238         break;
    239     }
    240   }
    241 
    242   for (PermissionMessages::const_iterator i = permissions.begin();
    243        i != permissions.end(); ++i) {
    244     int id = i->id();
    245     if (audio_capture && video_capture) {
    246       if (id == PermissionMessage::kAudioCapture) {
    247         messages.push_back(l10n_util::GetStringUTF16(
    248             IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
    249         continue;
    250       } else if (id == PermissionMessage::kVideoCapture) {
    251         // The combined message will be pushed above.
    252         continue;
    253       }
    254     }
    255     if (media_galleries_read && media_galleries_copy_to) {
    256       if (id == PermissionMessage::kMediaGalleriesAllGalleriesRead) {
    257         messages.push_back(l10n_util::GetStringUTF16(
    258             IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE));
    259         continue;
    260       } else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) {
    261         // The combined message will be pushed above.
    262         continue;
    263       }
    264     }
    265 
    266     messages.push_back(i->message());
    267   }
    268 
    269   return messages;
    270 }
    271 
    272 std::vector<string16> PermissionSet::GetWarningMessagesDetails(
    273     Manifest::Type extension_type) const {
    274   std::vector<string16> messages;
    275   PermissionMessages permissions = GetPermissionMessages(extension_type);
    276 
    277   for (PermissionMessages::const_iterator i = permissions.begin();
    278        i != permissions.end(); ++i)
    279     messages.push_back(i->details());
    280 
    281   return messages;
    282 }
    283 
    284 bool PermissionSet::IsEmpty() const {
    285   // Not default if any host permissions are present.
    286   if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
    287     return false;
    288 
    289   // Or if it has no api permissions.
    290   return apis().empty();
    291 }
    292 
    293 bool PermissionSet::HasAPIPermission(
    294     APIPermission::ID id) const {
    295   return apis().find(id) != apis().end();
    296 }
    297 
    298 bool PermissionSet::HasAPIPermission(const std::string& permission_name) const {
    299   const APIPermissionInfo* permission =
    300       PermissionsInfo::GetInstance()->GetByName(permission_name);
    301   CHECK(permission) << permission_name;
    302   return (permission && apis_.count(permission->id()));
    303 }
    304 
    305 bool PermissionSet::CheckAPIPermission(APIPermission::ID permission) const {
    306   return CheckAPIPermissionWithParam(permission, NULL);
    307 }
    308 
    309 bool PermissionSet::CheckAPIPermissionWithParam(
    310     APIPermission::ID permission,
    311     const APIPermission::CheckParam* param) const {
    312   APIPermissionSet::const_iterator iter = apis().find(permission);
    313   if (iter == apis().end())
    314     return false;
    315   return iter->Check(param);
    316 }
    317 
    318 bool PermissionSet::HasExplicitAccessToOrigin(
    319     const GURL& origin) const {
    320   return explicit_hosts().MatchesURL(origin);
    321 }
    322 
    323 bool PermissionSet::HasScriptableAccessToURL(
    324     const GURL& origin) const {
    325   // We only need to check our host list to verify access. The host list should
    326   // already reflect any special rules (such as chrome://favicon, all hosts
    327   // access, etc.).
    328   return scriptable_hosts().MatchesURL(origin);
    329 }
    330 
    331 bool PermissionSet::HasEffectiveAccessToAllHosts() const {
    332   // There are two ways this set can have effective access to all hosts:
    333   //  1) it has an <all_urls> URL pattern.
    334   //  2) it has a named permission with implied full URL access.
    335   for (URLPatternSet::const_iterator host = effective_hosts().begin();
    336        host != effective_hosts().end(); ++host) {
    337     if (host->match_all_urls() ||
    338         (host->match_subdomains() && host->host().empty()))
    339       return true;
    340   }
    341 
    342   for (APIPermissionSet::const_iterator i = apis().begin();
    343        i != apis().end(); ++i) {
    344     if (i->info()->implies_full_url_access())
    345       return true;
    346   }
    347   return false;
    348 }
    349 
    350 bool PermissionSet::HasEffectiveAccessToURL(const GURL& url) const {
    351   return effective_hosts().MatchesURL(url);
    352 }
    353 
    354 bool PermissionSet::HasEffectiveFullAccess() const {
    355   for (APIPermissionSet::const_iterator i = apis().begin();
    356        i != apis().end(); ++i) {
    357     if (i->info()->implies_full_access())
    358       return true;
    359   }
    360   return false;
    361 }
    362 
    363 bool PermissionSet::HasLessPrivilegesThan(
    364     const PermissionSet* permissions,
    365     Manifest::Type extension_type) const {
    366   // Things can't get worse than native code access.
    367   if (HasEffectiveFullAccess())
    368     return false;
    369 
    370   // Otherwise, it's a privilege increase if the new one has full access.
    371   if (permissions->HasEffectiveFullAccess())
    372     return true;
    373 
    374   if (HasLessHostPrivilegesThan(permissions, extension_type))
    375     return true;
    376 
    377   if (HasLessAPIPrivilegesThan(permissions))
    378     return true;
    379 
    380   return false;
    381 }
    382 
    383 PermissionSet::~PermissionSet() {}
    384 
    385 // static
    386 std::set<std::string> PermissionSet::GetDistinctHosts(
    387     const URLPatternSet& host_patterns,
    388     bool include_rcd,
    389     bool exclude_file_scheme) {
    390   // Use a vector to preserve order (also faster than a map on small sets).
    391   // Each item is a host split into two parts: host without RCDs and
    392   // current best RCD.
    393   typedef std::vector<std::pair<std::string, std::string> > HostVector;
    394   HostVector hosts_best_rcd;
    395   for (URLPatternSet::const_iterator i = host_patterns.begin();
    396        i != host_patterns.end(); ++i) {
    397     if (exclude_file_scheme && i->scheme() == chrome::kFileScheme)
    398       continue;
    399 
    400     std::string host = i->host();
    401 
    402     // Add the subdomain wildcard back to the host, if necessary.
    403     if (i->match_subdomains())
    404       host = "*." + host;
    405 
    406     // If the host has an RCD, split it off so we can detect duplicates.
    407     std::string rcd;
    408     size_t reg_len = net::registry_controlled_domains::GetRegistryLength(
    409         host,
    410         net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
    411         net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
    412     if (reg_len && reg_len != std::string::npos) {
    413       if (include_rcd)  // else leave rcd empty
    414         rcd = host.substr(host.size() - reg_len);
    415       host = host.substr(0, host.size() - reg_len);
    416     }
    417 
    418     // Check if we've already seen this host.
    419     HostVector::iterator it = hosts_best_rcd.begin();
    420     for (; it != hosts_best_rcd.end(); ++it) {
    421       if (it->first == host)
    422         break;
    423     }
    424     // If this host was found, replace the RCD if this one is better.
    425     if (it != hosts_best_rcd.end()) {
    426       if (include_rcd && RcdBetterThan(rcd, it->second))
    427         it->second = rcd;
    428     } else {  // Previously unseen host, append it.
    429       hosts_best_rcd.push_back(std::make_pair(host, rcd));
    430     }
    431   }
    432 
    433   // Build up the final vector by concatenating hosts and RCDs.
    434   std::set<std::string> distinct_hosts;
    435   for (HostVector::iterator it = hosts_best_rcd.begin();
    436        it != hosts_best_rcd.end(); ++it)
    437     distinct_hosts.insert(it->first + it->second);
    438   return distinct_hosts;
    439 }
    440 
    441 void PermissionSet::InitImplicitPermissions() {
    442   // The downloads permission implies the internal version as well.
    443   if (apis_.find(APIPermission::kDownloads) != apis_.end())
    444     apis_.insert(APIPermission::kDownloadsInternal);
    445 
    446   // TODO(fsamuel): Is there a better way to request access to the WebRequest
    447   // API without exposing it to the Chrome App?
    448   if (apis_.find(APIPermission::kWebView) != apis_.end())
    449     apis_.insert(APIPermission::kWebRequestInternal);
    450 
    451   // The webRequest permission implies the internal version as well.
    452   if (apis_.find(APIPermission::kWebRequest) != apis_.end())
    453     apis_.insert(APIPermission::kWebRequestInternal);
    454 
    455   // The fileBrowserHandler permission implies the internal version as well.
    456   if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
    457     apis_.insert(APIPermission::kFileBrowserHandlerInternal);
    458 }
    459 
    460 void PermissionSet::InitEffectiveHosts() {
    461   effective_hosts_.ClearPatterns();
    462 
    463   URLPatternSet::CreateUnion(
    464       explicit_hosts(), scriptable_hosts(), &effective_hosts_);
    465 }
    466 
    467 std::set<PermissionMessage> PermissionSet::GetAPIPermissionMessages() const {
    468   std::set<PermissionMessage> messages;
    469   for (APIPermissionSet::const_iterator permission_it = apis_.begin();
    470        permission_it != apis_.end(); ++permission_it) {
    471     DCHECK_GT(PermissionMessage::kNone,
    472               PermissionMessage::kUnknown);
    473     if (permission_it->HasMessages()) {
    474       PermissionMessages new_messages = permission_it->GetMessages();
    475       messages.insert(new_messages.begin(), new_messages.end());
    476     }
    477   }
    478   return messages;
    479 }
    480 
    481 std::set<PermissionMessage> PermissionSet::GetHostPermissionMessages(
    482     Manifest::Type extension_type) const {
    483   // Since platform apps always use isolated storage, they can't (silently)
    484   // access user data on other domains, so there's no need to prompt.
    485   // Note: this must remain consistent with HasLessHostPrivilegesThan.
    486   // See crbug.com/255229.
    487   std::set<PermissionMessage> messages;
    488   if (extension_type == Manifest::TYPE_PLATFORM_APP)
    489     return messages;
    490 
    491   if (HasEffectiveAccessToAllHosts()) {
    492     messages.insert(PermissionMessage(
    493         PermissionMessage::kHostsAll,
    494         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
    495   } else {
    496     PermissionMessages additional_warnings =
    497         GetChromeSchemePermissionWarnings(effective_hosts_);
    498     for (size_t i = 0; i < additional_warnings.size(); ++i)
    499       messages.insert(additional_warnings[i]);
    500 
    501     std::set<std::string> hosts = GetDistinctHostsForDisplay();
    502     if (!hosts.empty())
    503       messages.insert(PermissionMessage::CreateFromHostList(hosts));
    504   }
    505   return messages;
    506 }
    507 
    508 bool PermissionSet::HasLessAPIPrivilegesThan(
    509     const PermissionSet* permissions) const {
    510   if (permissions == NULL)
    511     return false;
    512 
    513   typedef std::set<PermissionMessage> PermissionMsgSet;
    514   PermissionMsgSet current_warnings = GetAPIPermissionMessages();
    515   PermissionMsgSet new_warnings = permissions->GetAPIPermissionMessages();
    516   PermissionMsgSet delta_warnings =
    517       base::STLSetDifference<PermissionMsgSet>(new_warnings, current_warnings);
    518 
    519   // We have less privileges if there are additional warnings present.
    520   return !delta_warnings.empty();
    521 }
    522 
    523 bool PermissionSet::HasLessHostPrivilegesThan(
    524     const PermissionSet* permissions,
    525     Manifest::Type extension_type) const {
    526   // Platform apps host permission changes do not count as privilege increases.
    527   // Note: this must remain consistent with GetHostPermissionMessages.
    528   if (extension_type == Manifest::TYPE_PLATFORM_APP)
    529     return false;
    530 
    531   // If this permission set can access any host, then it can't be elevated.
    532   if (HasEffectiveAccessToAllHosts())
    533     return false;
    534 
    535   // Likewise, if the other permission set has full host access, then it must be
    536   // a privilege increase.
    537   if (permissions->HasEffectiveAccessToAllHosts())
    538     return true;
    539 
    540   const URLPatternSet& old_list = effective_hosts();
    541   const URLPatternSet& new_list = permissions->effective_hosts();
    542 
    543   // TODO(jstritar): This is overly conservative with respect to subdomains.
    544   // For example, going from *.google.com to www.google.com will be
    545   // considered an elevation, even though it is not (http://crbug.com/65337).
    546   std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
    547   std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
    548   std::set<std::string> new_hosts_only;
    549 
    550   std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
    551                       old_hosts_set.begin(), old_hosts_set.end(),
    552                       std::inserter(new_hosts_only, new_hosts_only.begin()));
    553 
    554   return !new_hosts_only.empty();
    555 }
    556 
    557 }  // namespace extensions
    558