Home | History | Annotate | Download | only in permissions
      1 // Copyright 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/chrome_permission_message_provider.h"
      6 
      7 #include "base/stl_util.h"
      8 #include "base/strings/stringprintf.h"
      9 #include "extensions/common/extensions_client.h"
     10 #include "extensions/common/permissions/permission_message.h"
     11 #include "extensions/common/permissions/permission_message_util.h"
     12 #include "extensions/common/permissions/permission_set.h"
     13 #include "extensions/common/url_pattern.h"
     14 #include "extensions/common/url_pattern_set.h"
     15 #include "grit/generated_resources.h"
     16 #include "ui/base/l10n/l10n_util.h"
     17 #include "url/gurl.h"
     18 
     19 namespace extensions {
     20 
     21 namespace {
     22 
     23 typedef std::set<PermissionMessage> PermissionMsgSet;
     24 
     25 template<typename T>
     26 typename T::iterator FindMessageByID(T& messages, int id) {
     27   for (typename T::iterator it = messages.begin();
     28        it != messages.end(); ++it) {
     29     if (it->id() == id)
     30       return it;
     31   }
     32   return messages.end();
     33 }
     34 
     35 template<typename T>
     36 void SuppressMessage(T& messages,
     37                      int suppressing_message,
     38                      int suppressed_message) {
     39   typename T::iterator suppressed = FindMessageByID(messages,
     40                                                     suppressed_message);
     41   if (suppressed != messages.end() &&
     42       FindMessageByID(messages, suppressing_message) != messages.end()) {
     43     messages.erase(suppressed);
     44   }
     45 }
     46 
     47 }  // namespace
     48 
     49 ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
     50 }
     51 
     52 ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
     53 }
     54 
     55 PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages(
     56     const PermissionSet* permissions,
     57     Manifest::Type extension_type) const {
     58   PermissionMessages messages;
     59 
     60   if (permissions->HasEffectiveFullAccess()) {
     61     messages.push_back(PermissionMessage(
     62         PermissionMessage::kFullAccess,
     63         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
     64     return messages;
     65   }
     66 
     67   PermissionMsgSet host_msgs =
     68       GetHostPermissionMessages(permissions, extension_type);
     69   PermissionMsgSet api_msgs = GetAPIPermissionMessages(permissions);
     70   PermissionMsgSet manifest_permission_msgs =
     71       GetManifestPermissionMessages(permissions);
     72   messages.insert(messages.end(), host_msgs.begin(), host_msgs.end());
     73   messages.insert(messages.end(), api_msgs.begin(), api_msgs.end());
     74   messages.insert(messages.end(), manifest_permission_msgs.begin(),
     75                   manifest_permission_msgs.end());
     76 
     77   // Some warnings are more generic and/or powerful and superseed other
     78   // warnings. In that case, suppress the superseeded warning.
     79   SuppressMessage(messages,
     80                   PermissionMessage::kBookmarks,
     81                   PermissionMessage::kOverrideBookmarksUI);
     82   // Both tabs and history already allow reading favicons.
     83   SuppressMessage(messages,
     84                   PermissionMessage::kTabs,
     85                   PermissionMessage::kFavicon);
     86   SuppressMessage(messages,
     87                   PermissionMessage::kBrowsingHistory,
     88                   PermissionMessage::kFavicon);
     89   // Warning for history permission already covers warning for tabs permission.
     90   SuppressMessage(messages,
     91                   PermissionMessage::kBrowsingHistory,
     92                   PermissionMessage::kTabs);
     93   // Warning for full access permission already covers warning for tabs
     94   // permission.
     95   SuppressMessage(messages,
     96                   PermissionMessage::kHostsAll,
     97                   PermissionMessage::kTabs);
     98 
     99   return messages;
    100 }
    101 
    102 std::vector<base::string16> ChromePermissionMessageProvider::GetWarningMessages(
    103     const PermissionSet* permissions,
    104     Manifest::Type extension_type) const {
    105   std::vector<base::string16> message_strings;
    106   PermissionMessages messages =
    107       GetPermissionMessages(permissions, extension_type);
    108 
    109   bool audio_capture = false;
    110   bool video_capture = false;
    111   bool media_galleries_read = false;
    112   bool media_galleries_copy_to = false;
    113   bool media_galleries_delete = false;
    114   bool accessibility_read = false;
    115   bool accessibility_write = false;
    116   for (PermissionMessages::const_iterator i = messages.begin();
    117        i != messages.end(); ++i) {
    118     switch (i->id()) {
    119       case PermissionMessage::kAudioCapture:
    120         audio_capture = true;
    121         break;
    122       case PermissionMessage::kVideoCapture:
    123         video_capture = true;
    124         break;
    125       case PermissionMessage::kMediaGalleriesAllGalleriesRead:
    126         media_galleries_read = true;
    127         break;
    128       case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo:
    129         media_galleries_copy_to = true;
    130         break;
    131       case PermissionMessage::kMediaGalleriesAllGalleriesDelete:
    132         media_galleries_delete = true;
    133         break;
    134       case PermissionMessage::kAccessibilityFeaturesRead:
    135         accessibility_read = true;
    136         break;
    137       case PermissionMessage::kAccessibilityFeaturesModify:
    138         accessibility_write = true;
    139         break;
    140       default:
    141         break;
    142     }
    143   }
    144 
    145   for (PermissionMessages::const_iterator i = messages.begin();
    146        i != messages.end(); ++i) {
    147     int id = i->id();
    148     if (audio_capture && video_capture) {
    149       if (id == PermissionMessage::kAudioCapture) {
    150         message_strings.push_back(l10n_util::GetStringUTF16(
    151             IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
    152         continue;
    153       } else if (id == PermissionMessage::kVideoCapture) {
    154         // The combined message will be pushed above.
    155         continue;
    156       }
    157     }
    158     if (accessibility_read && accessibility_write) {
    159       if (id == PermissionMessage::kAccessibilityFeaturesRead) {
    160         message_strings.push_back(l10n_util::GetStringUTF16(
    161             IDS_EXTENSION_PROMPT_WARNING_ACCESSIBILITY_FEATURES_READ_MODIFY));
    162         continue;
    163       } else if (id == PermissionMessage::kAccessibilityFeaturesModify) {
    164         // The combined message will be pushed above.
    165         continue;
    166       }
    167     }
    168     if (media_galleries_read &&
    169         (media_galleries_copy_to || media_galleries_delete)) {
    170       if (id == PermissionMessage::kMediaGalleriesAllGalleriesRead) {
    171         int m_id = media_galleries_copy_to ?
    172             IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE :
    173             IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_DELETE;
    174         message_strings.push_back(l10n_util::GetStringUTF16(m_id));
    175         continue;
    176       } else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo ||
    177                  id == PermissionMessage::kMediaGalleriesAllGalleriesDelete) {
    178         // The combined message will be pushed above.
    179         continue;
    180       }
    181     }
    182     if (permissions->HasAPIPermission(APIPermission::kSessions) &&
    183         id == PermissionMessage::kTabs) {
    184       message_strings.push_back(l10n_util::GetStringUTF16(
    185           IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_AND_SESSIONS));
    186       continue;
    187     }
    188     if (permissions->HasAPIPermission(APIPermission::kSessions) &&
    189         id == PermissionMessage::kBrowsingHistory) {
    190       message_strings.push_back(l10n_util::GetStringUTF16(
    191           IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE_AND_SESSIONS));
    192       continue;
    193     }
    194 
    195     message_strings.push_back(i->message());
    196   }
    197 
    198   return message_strings;
    199 }
    200 
    201 std::vector<base::string16>
    202 ChromePermissionMessageProvider::GetWarningMessagesDetails(
    203     const PermissionSet* permissions,
    204     Manifest::Type extension_type) const {
    205   std::vector<base::string16> message_strings;
    206   PermissionMessages messages =
    207       GetPermissionMessages(permissions, extension_type);
    208 
    209   for (PermissionMessages::const_iterator i = messages.begin();
    210        i != messages.end(); ++i)
    211     message_strings.push_back(i->details());
    212 
    213   return message_strings;
    214 }
    215 
    216 bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
    217     const PermissionSet* old_permissions,
    218     const PermissionSet* new_permissions,
    219     Manifest::Type extension_type) const {
    220   // Things can't get worse than native code access.
    221   if (old_permissions->HasEffectiveFullAccess())
    222     return false;
    223 
    224   // Otherwise, it's a privilege increase if the new one has full access.
    225   if (new_permissions->HasEffectiveFullAccess())
    226     return true;
    227 
    228   if (IsHostPrivilegeIncrease(old_permissions, new_permissions, extension_type))
    229     return true;
    230 
    231   if (IsAPIPrivilegeIncrease(old_permissions, new_permissions))
    232     return true;
    233 
    234   if (IsManifestPermissionPrivilegeIncrease(old_permissions, new_permissions))
    235     return true;
    236 
    237   return false;
    238 }
    239 
    240 std::set<PermissionMessage>
    241 ChromePermissionMessageProvider::GetAPIPermissionMessages(
    242     const PermissionSet* permissions) const {
    243   PermissionMsgSet messages;
    244   for (APIPermissionSet::const_iterator permission_it =
    245            permissions->apis().begin();
    246        permission_it != permissions->apis().end(); ++permission_it) {
    247     if (permission_it->HasMessages()) {
    248       PermissionMessages new_messages = permission_it->GetMessages();
    249       messages.insert(new_messages.begin(), new_messages.end());
    250     }
    251   }
    252 
    253   // A special hack: If kFileSystemWriteDirectory would be displayed, hide
    254   // kFileSystemDirectory as the write directory message implies it.
    255   // TODO(sammc): Remove this. See http://crbug.com/284849.
    256   SuppressMessage(messages,
    257                   PermissionMessage::kFileSystemWriteDirectory,
    258                   PermissionMessage::kFileSystemDirectory);
    259   // A special hack: The warning message for declarativeWebRequest
    260   // permissions speaks about blocking parts of pages, which is a
    261   // subset of what the "<all_urls>" access allows. Therefore we
    262   // display only the "<all_urls>" warning message if both permissions
    263   // are required.
    264   if (permissions->ShouldWarnAllHosts()) {
    265     messages.erase(
    266         PermissionMessage(
    267             PermissionMessage::kDeclarativeWebRequest, base::string16()));
    268   }
    269   return messages;
    270 }
    271 
    272 std::set<PermissionMessage>
    273 ChromePermissionMessageProvider::GetManifestPermissionMessages(
    274     const PermissionSet* permissions) const {
    275   PermissionMsgSet messages;
    276   for (ManifestPermissionSet::const_iterator permission_it =
    277            permissions->manifest_permissions().begin();
    278       permission_it != permissions->manifest_permissions().end();
    279       ++permission_it) {
    280     if (permission_it->HasMessages()) {
    281       PermissionMessages new_messages = permission_it->GetMessages();
    282       messages.insert(new_messages.begin(), new_messages.end());
    283     }
    284   }
    285   return messages;
    286 }
    287 
    288 std::set<PermissionMessage>
    289 ChromePermissionMessageProvider::GetHostPermissionMessages(
    290     const PermissionSet* permissions,
    291     Manifest::Type extension_type) const {
    292   PermissionMsgSet messages;
    293   // Since platform apps always use isolated storage, they can't (silently)
    294   // access user data on other domains, so there's no need to prompt.
    295   // Note: this must remain consistent with IsHostPrivilegeIncrease.
    296   // See crbug.com/255229.
    297   if (extension_type == Manifest::TYPE_PLATFORM_APP)
    298     return messages;
    299 
    300   if (permissions->ShouldWarnAllHosts()) {
    301     messages.insert(PermissionMessage(
    302         PermissionMessage::kHostsAll,
    303         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
    304   } else {
    305     URLPatternSet regular_hosts;
    306     ExtensionsClient::Get()->FilterHostPermissions(
    307         permissions->effective_hosts(), &regular_hosts, &messages);
    308 
    309     std::set<std::string> hosts =
    310         permission_message_util::GetDistinctHosts(regular_hosts, true, true);
    311     if (!hosts.empty())
    312       messages.insert(permission_message_util::CreateFromHostList(hosts));
    313   }
    314   return messages;
    315 }
    316 
    317 bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease(
    318     const PermissionSet* old_permissions,
    319     const PermissionSet* new_permissions) const {
    320   if (new_permissions == NULL)
    321     return false;
    322 
    323   PermissionMsgSet old_warnings = GetAPIPermissionMessages(old_permissions);
    324   PermissionMsgSet new_warnings = GetAPIPermissionMessages(new_permissions);
    325   PermissionMsgSet delta_warnings =
    326       base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings);
    327 
    328   // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory.
    329   // TODO(sammc): Remove this. See http://crbug.com/284849.
    330   if (old_warnings.find(PermissionMessage(
    331           PermissionMessage::kFileSystemWriteDirectory, base::string16())) !=
    332       old_warnings.end()) {
    333     delta_warnings.erase(
    334         PermissionMessage(PermissionMessage::kFileSystemDirectory,
    335                           base::string16()));
    336   }
    337 
    338   // It is a privilege increase if there are additional warnings present.
    339   return !delta_warnings.empty();
    340 }
    341 
    342 bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease(
    343     const PermissionSet* old_permissions,
    344     const PermissionSet* new_permissions) const {
    345   if (new_permissions == NULL)
    346     return false;
    347 
    348   PermissionMsgSet old_warnings =
    349       GetManifestPermissionMessages(old_permissions);
    350   PermissionMsgSet new_warnings =
    351       GetManifestPermissionMessages(new_permissions);
    352   PermissionMsgSet delta_warnings =
    353       base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings);
    354 
    355   // It is a privilege increase if there are additional warnings present.
    356   return !delta_warnings.empty();
    357 }
    358 
    359 bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
    360     const PermissionSet* old_permissions,
    361     const PermissionSet* new_permissions,
    362     Manifest::Type extension_type) const {
    363   // Platform apps host permission changes do not count as privilege increases.
    364   // Note: this must remain consistent with GetHostPermissionMessages.
    365   if (extension_type == Manifest::TYPE_PLATFORM_APP)
    366     return false;
    367 
    368   // If the old permission set can access any host, then it can't be elevated.
    369   if (old_permissions->HasEffectiveAccessToAllHosts())
    370     return false;
    371 
    372   // Likewise, if the new permission set has full host access, then it must be
    373   // a privilege increase.
    374   if (new_permissions->HasEffectiveAccessToAllHosts())
    375     return true;
    376 
    377   const URLPatternSet& old_list = old_permissions->effective_hosts();
    378   const URLPatternSet& new_list = new_permissions->effective_hosts();
    379 
    380   // TODO(jstritar): This is overly conservative with respect to subdomains.
    381   // For example, going from *.google.com to www.google.com will be
    382   // considered an elevation, even though it is not (http://crbug.com/65337).
    383   std::set<std::string> new_hosts_set(
    384       permission_message_util::GetDistinctHosts(new_list, false, false));
    385   std::set<std::string> old_hosts_set(
    386       permission_message_util::GetDistinctHosts(old_list, false, false));
    387   std::set<std::string> new_hosts_only =
    388       base::STLSetDifference<std::set<std::string> >(new_hosts_set,
    389                                                      old_hosts_set);
    390 
    391   return !new_hosts_only.empty();
    392 }
    393 
    394 }  // namespace extensions
    395