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