Remove INJECT_EVENTS permissions from test APKs.
[chromium-blink-merge.git] / chrome / common / extensions / permissions / chrome_permission_message_provider.cc
blob7bf445fb29b7c1d7b75a3c9abe11794d1f09475f
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.
5 #include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
7 #include "base/memory/scoped_vector.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "chrome/common/extensions/permissions/chrome_permission_message_rules.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "extensions/common/extensions_client.h"
15 #include "extensions/common/permissions/permission_message.h"
16 #include "extensions/common/permissions/permission_message_util.h"
17 #include "extensions/common/permissions/permission_set.h"
18 #include "extensions/common/url_pattern.h"
19 #include "extensions/common/url_pattern_set.h"
20 #include "grit/extensions_strings.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "url/gurl.h"
24 namespace extensions {
26 namespace {
28 typedef std::set<PermissionMessage> PermissionMsgSet;
30 template<typename T>
31 typename T::iterator FindMessageByID(T& messages, int id) {
32 for (typename T::iterator it = messages.begin();
33 it != messages.end(); ++it) {
34 if (it->id() == id)
35 return it;
37 return messages.end();
40 template<typename T>
41 typename T::const_iterator FindMessageByID(const T& messages, int id) {
42 for (typename T::const_iterator it = messages.begin();
43 it != messages.end(); ++it) {
44 if (it->id() == id)
45 return it;
47 return messages.end();
50 template<typename T>
51 void SuppressMessage(T& messages,
52 int suppressing_message,
53 int suppressed_message) {
54 typename T::iterator suppressed = FindMessageByID(messages,
55 suppressed_message);
56 if (suppressed != messages.end() &&
57 FindMessageByID(messages, suppressing_message) != messages.end()) {
58 messages.erase(suppressed);
62 bool ContainsMessages(const PermissionMessages& messages,
63 int first_message,
64 int second_message) {
65 return FindMessageByID(messages, first_message) != messages.end() &&
66 FindMessageByID(messages, second_message) != messages.end();
69 bool ContainsMessages(const PermissionMessages& messages,
70 int first_message,
71 int second_message,
72 int third_message) {
73 return ContainsMessages(messages, first_message, second_message) &&
74 FindMessageByID(messages, third_message) != messages.end();
77 } // namespace
79 ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
82 ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
85 PermissionMessages ChromePermissionMessageProvider::GetLegacyPermissionMessages(
86 const PermissionSet* permissions,
87 Manifest::Type extension_type) const {
88 PermissionMessages messages;
89 // TODO(sashab): Check if this the correct logic - if an app has effective
90 // full access (i.e. the plugin permission), do we not want to display *any*
91 // other permission messages?
92 if (permissions->HasEffectiveFullAccess()) {
93 messages.push_back(PermissionMessage(
94 PermissionMessage::kFullAccess,
95 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
96 return messages;
99 // Some warnings are more generic and/or powerful and supercede other
100 // warnings. In that case, the first message suppresses the second one.
101 // WARNING: When modifying a rule in this list, be sure to also modify the
102 // rule in ChromePermissionMessageProvider::GetCoalescedPermissionMessages.
103 // TODO(sashab): Deprecate this function, and remove this list.
104 std::multimap<PermissionMessage::ID, PermissionMessage::ID> kSuppressList;
105 kSuppressList.insert(
106 {PermissionMessage::kBluetooth, PermissionMessage::kBluetoothDevices});
107 kSuppressList.insert(
108 {PermissionMessage::kBookmarks, PermissionMessage::kOverrideBookmarksUI});
109 // History already allows reading favicons.
110 kSuppressList.insert(
111 {PermissionMessage::kBrowsingHistory, PermissionMessage::kFavicon});
112 // History already allows tabs access.
113 kSuppressList.insert(
114 {PermissionMessage::kBrowsingHistory, PermissionMessage::kTabs});
115 // History already allows access the list of most frequently visited sites.
116 kSuppressList.insert(
117 {PermissionMessage::kBrowsingHistory, PermissionMessage::kTopSites});
118 // A special hack: If kFileSystemWriteDirectory would be displayed, hide
119 // kFileSystemDirectory as the write directory message implies it.
120 // TODO(sammc): Remove this. See http://crbug.com/284849.
121 kSuppressList.insert({PermissionMessage::kFileSystemWriteDirectory,
122 PermissionMessage::kFileSystemDirectory});
123 // Full access already allows DeclarativeWebRequest.
124 kSuppressList.insert({PermissionMessage::kHostsAll,
125 PermissionMessage::kDeclarativeWebRequest});
126 // Full access implies reading the list of most frequently visited sites.
127 kSuppressList.insert(
128 {PermissionMessage::kHostsAll, PermissionMessage::kTopSites});
129 // Full access already covers tabs access.
130 kSuppressList.insert(
131 {PermissionMessage::kHostsAll, PermissionMessage::kTabs});
132 // Tabs already allows reading favicons.
133 kSuppressList.insert({PermissionMessage::kTabs, PermissionMessage::kFavicon});
134 // Tabs already allows reading the list of most frequently visited sites.
135 kSuppressList.insert(
136 {PermissionMessage::kTabs, PermissionMessage::kTopSites});
138 PermissionMsgSet host_msgs =
139 GetHostPermissionMessages(permissions, NULL, extension_type);
140 PermissionMsgSet api_msgs =
141 GetAPIPermissionMessages(permissions, NULL, extension_type);
142 PermissionMsgSet manifest_permission_msgs =
143 GetManifestPermissionMessages(permissions, NULL);
144 messages.insert(messages.end(), host_msgs.begin(), host_msgs.end());
145 messages.insert(messages.end(), api_msgs.begin(), api_msgs.end());
146 messages.insert(messages.end(), manifest_permission_msgs.begin(),
147 manifest_permission_msgs.end());
149 for (std::multimap<PermissionMessage::ID,
150 PermissionMessage::ID>::const_iterator it =
151 kSuppressList.begin();
152 it != kSuppressList.end();
153 ++it) {
154 SuppressMessage(messages, it->first, it->second);
157 return messages;
160 PermissionMessageIDs
161 ChromePermissionMessageProvider::GetLegacyPermissionMessageIDs(
162 const PermissionSet* permissions,
163 Manifest::Type extension_type) const {
164 PermissionMessageIDs ids;
165 for (const PermissionMessage& msg :
166 GetLegacyPermissionMessages(permissions, extension_type)) {
167 ids.push_back(msg.id());
169 return ids;
172 CoalescedPermissionMessages
173 ChromePermissionMessageProvider::GetCoalescedPermissionMessages(
174 const PermissionIDSet& permissions) const {
175 std::vector<ChromePermissionMessageRule> rules =
176 ChromePermissionMessageRule::GetAllRules();
178 // Apply each of the rules, in order, to generate the messages for the given
179 // permissions. Once a permission is used in a rule, remove it from the set
180 // of available permissions so it cannot be applied to subsequent rules.
181 PermissionIDSet remaining_permissions = permissions;
182 CoalescedPermissionMessages messages;
183 for (const auto& rule : rules) {
184 // Only apply the rule if we have all the required permission IDs.
185 if (remaining_permissions.ContainsAllIDs(rule.required_permissions())) {
186 // We can apply the rule. Add all the required permissions, and as many
187 // optional permissions as we can, to the new message.
188 PermissionIDSet used_permissions =
189 remaining_permissions.GetAllPermissionsWithIDs(
190 rule.all_permissions());
191 messages.push_back(
192 rule.formatter()->GetPermissionMessage(used_permissions));
194 remaining_permissions =
195 PermissionIDSet::Difference(remaining_permissions, used_permissions);
199 return messages;
202 std::vector<base::string16>
203 ChromePermissionMessageProvider::GetLegacyWarningMessages(
204 const PermissionSet* permissions,
205 Manifest::Type extension_type) const {
206 std::vector<base::string16> message_strings;
207 std::vector<base::string16> message_details_strings;
208 CoalesceWarningMessages(permissions,
209 extension_type,
210 &message_strings,
211 &message_details_strings);
212 return message_strings;
215 std::vector<base::string16>
216 ChromePermissionMessageProvider::GetLegacyWarningMessagesDetails(
217 const PermissionSet* permissions,
218 Manifest::Type extension_type) const {
219 std::vector<base::string16> message_strings;
220 std::vector<base::string16> message_details_strings;
221 CoalesceWarningMessages(permissions,
222 extension_type,
223 &message_strings,
224 &message_details_strings);
225 return message_details_strings;
228 bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
229 const PermissionSet* old_permissions,
230 const PermissionSet* new_permissions,
231 Manifest::Type extension_type) const {
232 // Things can't get worse than native code access.
233 if (old_permissions->HasEffectiveFullAccess())
234 return false;
236 // Otherwise, it's a privilege increase if the new one has full access.
237 if (new_permissions->HasEffectiveFullAccess())
238 return true;
240 if (IsHostPrivilegeIncrease(old_permissions, new_permissions, extension_type))
241 return true;
243 if (IsAPIPrivilegeIncrease(old_permissions, new_permissions, extension_type))
244 return true;
246 if (IsManifestPermissionPrivilegeIncrease(old_permissions, new_permissions))
247 return true;
249 return false;
252 PermissionIDSet ChromePermissionMessageProvider::GetAllPermissionIDs(
253 const PermissionSet* permissions,
254 Manifest::Type extension_type) const {
255 PermissionIDSet permission_ids;
256 GetAPIPermissionMessages(permissions, &permission_ids, extension_type);
257 GetManifestPermissionMessages(permissions, &permission_ids);
258 GetHostPermissionMessages(permissions, &permission_ids, extension_type);
259 return permission_ids;
262 std::set<PermissionMessage>
263 ChromePermissionMessageProvider::GetAPIPermissionMessages(
264 const PermissionSet* permissions,
265 PermissionIDSet* permission_ids,
266 Manifest::Type extension_type) const {
267 PermissionMsgSet messages;
268 for (APIPermissionSet::const_iterator permission_it =
269 permissions->apis().begin();
270 permission_it != permissions->apis().end(); ++permission_it) {
271 if (permission_ids != NULL)
272 permission_ids->InsertAll(permission_it->GetPermissions());
273 if (permission_it->HasMessages()) {
274 PermissionMessages new_messages = permission_it->GetMessages();
275 messages.insert(new_messages.begin(), new_messages.end());
279 // A special hack: The warning message for declarativeWebRequest
280 // permissions speaks about blocking parts of pages, which is a
281 // subset of what the "<all_urls>" access allows. Therefore we
282 // display only the "<all_urls>" warning message if both permissions
283 // are required.
284 if (permissions->ShouldWarnAllHosts()) {
285 // Platform apps don't show hosts warnings. See crbug.com/255229.
286 if (permission_ids != NULL && extension_type != Manifest::TYPE_PLATFORM_APP)
287 permission_ids->insert(APIPermission::kHostsAll);
288 messages.erase(
289 PermissionMessage(
290 PermissionMessage::kDeclarativeWebRequest, base::string16()));
292 return messages;
295 std::set<PermissionMessage>
296 ChromePermissionMessageProvider::GetManifestPermissionMessages(
297 const PermissionSet* permissions,
298 PermissionIDSet* permission_ids) const {
299 PermissionMsgSet messages;
300 for (ManifestPermissionSet::const_iterator permission_it =
301 permissions->manifest_permissions().begin();
302 permission_it != permissions->manifest_permissions().end();
303 ++permission_it) {
304 if (permission_ids != NULL)
305 permission_ids->InsertAll(permission_it->GetPermissions());
306 if (permission_it->HasMessages()) {
307 PermissionMessages new_messages = permission_it->GetMessages();
308 messages.insert(new_messages.begin(), new_messages.end());
311 return messages;
314 std::set<PermissionMessage>
315 ChromePermissionMessageProvider::GetHostPermissionMessages(
316 const PermissionSet* permissions,
317 PermissionIDSet* permission_ids,
318 Manifest::Type extension_type) const {
319 PermissionMsgSet messages;
320 // Since platform apps always use isolated storage, they can't (silently)
321 // access user data on other domains, so there's no need to prompt.
322 // Note: this must remain consistent with IsHostPrivilegeIncrease.
323 // See crbug.com/255229.
324 if (extension_type == Manifest::TYPE_PLATFORM_APP)
325 return messages;
327 if (permissions->ShouldWarnAllHosts()) {
328 if (permission_ids != NULL)
329 permission_ids->insert(APIPermission::kHostsAll);
330 messages.insert(PermissionMessage(
331 PermissionMessage::kHostsAll,
332 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
333 } else {
334 URLPatternSet regular_hosts;
335 ExtensionsClient::Get()->FilterHostPermissions(
336 permissions->effective_hosts(), &regular_hosts, &messages);
337 if (permission_ids != NULL) {
338 ExtensionsClient::Get()->FilterHostPermissions(
339 permissions->effective_hosts(), &regular_hosts, permission_ids);
342 std::set<std::string> hosts =
343 permission_message_util::GetDistinctHosts(regular_hosts, true, true);
344 if (!hosts.empty()) {
345 if (permission_ids != NULL) {
346 permission_message_util::AddHostPermissions(
347 permission_ids, hosts, permission_message_util::kReadWrite);
349 messages.insert(permission_message_util::CreateFromHostList(
350 hosts, permission_message_util::kReadWrite));
353 return messages;
356 void ChromePermissionMessageProvider::CoalesceWarningMessages(
357 const PermissionSet* permissions,
358 Manifest::Type extension_type,
359 std::vector<base::string16>* message_strings,
360 std::vector<base::string16>* message_details_strings) const {
361 PermissionMessages messages =
362 GetLegacyPermissionMessages(permissions, extension_type);
364 // WARNING: When modifying a coalescing rule in this list, be sure to also
365 // modify the corresponding rule in
366 // ChromePermissionMessageProvider::GetCoalescedPermissionMessages().
367 // TODO(sashab): Deprecate this function, and remove this list.
368 for (PermissionMessages::const_iterator i = messages.begin();
369 i != messages.end(); ++i) {
370 int id = i->id();
371 // Access to users' devices should provide a single warning message
372 // specifying the transport method used; serial and/or Bluetooth.
373 if (id == PermissionMessage::kBluetooth ||
374 id == PermissionMessage::kSerial) {
375 if (ContainsMessages(messages,
376 PermissionMessage::kBluetooth,
377 PermissionMessage::kSerial)) {
378 if (id == PermissionMessage::kBluetooth) {
379 message_strings->push_back(l10n_util::GetStringUTF16(
380 IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH_SERIAL));
381 message_details_strings->push_back(base::string16());
383 continue;
386 if (id == PermissionMessage::kAccessibilityFeaturesModify ||
387 id == PermissionMessage::kAccessibilityFeaturesRead) {
388 if (ContainsMessages(messages,
389 PermissionMessage::kAccessibilityFeaturesModify,
390 PermissionMessage::kAccessibilityFeaturesRead)) {
391 if (id == PermissionMessage::kAccessibilityFeaturesModify) {
392 message_strings->push_back(l10n_util::GetStringUTF16(
393 IDS_EXTENSION_PROMPT_WARNING_ACCESSIBILITY_FEATURES_READ_MODIFY));
394 message_details_strings->push_back(base::string16());
396 continue;
399 if (id == PermissionMessage::kAudioCapture ||
400 id == PermissionMessage::kVideoCapture) {
401 if (ContainsMessages(messages,
402 PermissionMessage::kAudioCapture,
403 PermissionMessage::kVideoCapture)) {
404 if (id == PermissionMessage::kAudioCapture) {
405 message_strings->push_back(l10n_util::GetStringUTF16(
406 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
407 message_details_strings->push_back(base::string16());
409 continue;
412 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo ||
413 id == PermissionMessage::kMediaGalleriesAllGalleriesDelete ||
414 id == PermissionMessage::kMediaGalleriesAllGalleriesRead) {
415 if (ContainsMessages(
416 messages,
417 PermissionMessage::kMediaGalleriesAllGalleriesCopyTo,
418 PermissionMessage::kMediaGalleriesAllGalleriesDelete,
419 PermissionMessage::kMediaGalleriesAllGalleriesRead)) {
420 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) {
421 message_strings->push_back(l10n_util::GetStringUTF16(
422 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE_DELETE));
423 message_details_strings->push_back(base::string16());
425 continue;
427 if (ContainsMessages(
428 messages,
429 PermissionMessage::kMediaGalleriesAllGalleriesCopyTo,
430 PermissionMessage::kMediaGalleriesAllGalleriesRead)) {
431 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) {
432 message_strings->push_back(l10n_util::GetStringUTF16(
433 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE));
434 message_details_strings->push_back(base::string16());
436 continue;
438 if (ContainsMessages(
439 messages,
440 PermissionMessage::kMediaGalleriesAllGalleriesDelete,
441 PermissionMessage::kMediaGalleriesAllGalleriesRead)) {
442 if (id == PermissionMessage::kMediaGalleriesAllGalleriesDelete) {
443 message_strings->push_back(l10n_util::GetStringUTF16(
444 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_DELETE));
445 message_details_strings->push_back(base::string16());
447 continue;
450 if (permissions->HasAPIPermission(APIPermission::kSessions) &&
451 id == PermissionMessage::kTabs) {
452 message_strings->push_back(l10n_util::GetStringUTF16(
453 IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_AND_SESSIONS));
454 message_details_strings->push_back(base::string16());
455 continue;
457 if (permissions->HasAPIPermission(APIPermission::kSessions) &&
458 id == PermissionMessage::kBrowsingHistory) {
459 message_strings->push_back(l10n_util::GetStringUTF16(
460 IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE_AND_SESSIONS));
461 message_details_strings->push_back(base::string16());
462 continue;
465 message_strings->push_back(i->message());
466 message_details_strings->push_back(i->details());
470 bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease(
471 const PermissionSet* old_permissions,
472 const PermissionSet* new_permissions,
473 Manifest::Type extension_type) const {
474 if (new_permissions == NULL)
475 return false;
477 PermissionMsgSet old_warnings =
478 GetAPIPermissionMessages(old_permissions, NULL, extension_type);
479 PermissionMsgSet new_warnings =
480 GetAPIPermissionMessages(new_permissions, NULL, extension_type);
481 PermissionMsgSet delta_warnings =
482 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings);
484 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory.
485 // TODO(sammc): Remove this. See http://crbug.com/284849.
486 if (old_warnings.find(PermissionMessage(
487 PermissionMessage::kFileSystemWriteDirectory, base::string16())) !=
488 old_warnings.end()) {
489 delta_warnings.erase(
490 PermissionMessage(PermissionMessage::kFileSystemDirectory,
491 base::string16()));
494 // It is a privilege increase if there are additional warnings present.
495 return !delta_warnings.empty();
498 bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease(
499 const PermissionSet* old_permissions,
500 const PermissionSet* new_permissions) const {
501 if (new_permissions == NULL)
502 return false;
504 PermissionMsgSet old_warnings =
505 GetManifestPermissionMessages(old_permissions, NULL);
506 PermissionMsgSet new_warnings =
507 GetManifestPermissionMessages(new_permissions, NULL);
508 PermissionMsgSet delta_warnings =
509 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings);
511 // It is a privilege increase if there are additional warnings present.
512 return !delta_warnings.empty();
515 bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
516 const PermissionSet* old_permissions,
517 const PermissionSet* new_permissions,
518 Manifest::Type extension_type) const {
519 // Platform apps host permission changes do not count as privilege increases.
520 // Note: this must remain consistent with GetHostPermissionMessages.
521 if (extension_type == Manifest::TYPE_PLATFORM_APP)
522 return false;
524 // If the old permission set can access any host, then it can't be elevated.
525 if (old_permissions->HasEffectiveAccessToAllHosts())
526 return false;
528 // Likewise, if the new permission set has full host access, then it must be
529 // a privilege increase.
530 if (new_permissions->HasEffectiveAccessToAllHosts())
531 return true;
533 const URLPatternSet& old_list = old_permissions->effective_hosts();
534 const URLPatternSet& new_list = new_permissions->effective_hosts();
536 // TODO(jstritar): This is overly conservative with respect to subdomains.
537 // For example, going from *.google.com to www.google.com will be
538 // considered an elevation, even though it is not (http://crbug.com/65337).
539 std::set<std::string> new_hosts_set(
540 permission_message_util::GetDistinctHosts(new_list, false, false));
541 std::set<std::string> old_hosts_set(
542 permission_message_util::GetDistinctHosts(old_list, false, false));
543 std::set<std::string> new_hosts_only =
544 base::STLSetDifference<std::set<std::string> >(new_hosts_set,
545 old_hosts_set);
547 return !new_hosts_only.empty();
550 } // namespace extensions