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