chrome_permission_message_provider.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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>
36void 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
49ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
50}
51
52ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
53}
54
55PermissionMessages 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
102std::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
201std::vector<base::string16>
202ChromePermissionMessageProvider::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
216bool 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
240std::set<PermissionMessage>
241ChromePermissionMessageProvider::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
272std::set<PermissionMessage>
273ChromePermissionMessageProvider::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
288std::set<PermissionMessage>
289ChromePermissionMessageProvider::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
317bool 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
342bool 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
359bool 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