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"
24 namespace extensions
{
28 typedef std::set
<PermissionMessage
> PermissionMsgSet
;
31 typename
T::iterator
FindMessageByID(T
& messages
, int id
) {
32 for (typename
T::iterator it
= messages
.begin();
33 it
!= messages
.end(); ++it
) {
37 return messages
.end();
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
) {
47 return messages
.end();
51 void SuppressMessage(T
& messages
,
52 int suppressing_message
,
53 int suppressed_message
) {
54 typename
T::iterator suppressed
= FindMessageByID(messages
,
56 if (suppressed
!= messages
.end() &&
57 FindMessageByID(messages
, suppressing_message
) != messages
.end()) {
58 messages
.erase(suppressed
);
62 bool ContainsMessages(const PermissionMessages
& messages
,
65 return FindMessageByID(messages
, first_message
) != messages
.end() &&
66 FindMessageByID(messages
, second_message
) != messages
.end();
69 bool ContainsMessages(const PermissionMessages
& messages
,
73 return ContainsMessages(messages
, first_message
, second_message
) &&
74 FindMessageByID(messages
, third_message
) != messages
.end();
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
)));
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();
154 SuppressMessage(messages
, it
->first
, it
->second
);
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());
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());
192 rule
.formatter()->GetPermissionMessage(used_permissions
));
194 remaining_permissions
=
195 PermissionIDSet::Difference(remaining_permissions
, used_permissions
);
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
,
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
,
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())
236 // Otherwise, it's a privilege increase if the new one has full access.
237 if (new_permissions
->HasEffectiveFullAccess())
240 if (IsHostPrivilegeIncrease(old_permissions
, new_permissions
, extension_type
))
243 if (IsAPIPrivilegeIncrease(old_permissions
, new_permissions
, extension_type
))
246 if (IsManifestPermissionPrivilegeIncrease(old_permissions
, new_permissions
))
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
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
);
290 PermissionMessage::kDeclarativeWebRequest
, base::string16()));
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();
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());
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
)
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
)));
334 URLPatternSet regular_hosts
;
335 ExtensionsClient::Get()->FilterHostPermissions(
336 permissions
->effective_hosts(), ®ular_hosts
, &messages
);
337 if (permission_ids
!= NULL
) {
338 ExtensionsClient::Get()->FilterHostPermissions(
339 permissions
->effective_hosts(), ®ular_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
));
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
) {
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());
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());
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());
412 if (id
== PermissionMessage::kMediaGalleriesAllGalleriesCopyTo
||
413 id
== PermissionMessage::kMediaGalleriesAllGalleriesDelete
||
414 id
== PermissionMessage::kMediaGalleriesAllGalleriesRead
) {
415 if (ContainsMessages(
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());
427 if (ContainsMessages(
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());
438 if (ContainsMessages(
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());
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());
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());
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
)
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
,
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
)
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
)
524 // If the old permission set can access any host, then it can't be elevated.
525 if (old_permissions
->HasEffectiveAccessToAllHosts())
528 // Likewise, if the new permission set has full host access, then it must be
529 // a privilege increase.
530 if (new_permissions
->HasEffectiveAccessToAllHosts())
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
,
547 return !new_hosts_only
.empty();
550 } // namespace extensions