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