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