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/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 "net/base/registry_controlled_domains/registry_controlled_domain.h"
17 #include "ui/base/l10n/l10n_util.h"
20 namespace extensions
{
24 typedef std::set
<PermissionMessage
> PermissionMsgSet
;
26 bool ShouldWarnAllHosts(const PermissionSet
* permissions
) {
27 if (permissions
->HasEffectiveAccessToAllHosts())
30 const URLPatternSet
& effective_hosts
= permissions
->effective_hosts();
31 for (URLPatternSet::const_iterator iter
= effective_hosts
.begin();
32 iter
!= effective_hosts
.end();
34 // If this doesn't even match subdomains, it can't possibly imply all hosts.
35 if (!iter
->match_subdomains())
38 // If iter->host() is a recognized TLD, this will be 0. We don't include
39 // private TLDs, so that, e.g., *.appspot.com does not imply all hosts.
40 size_t registry_length
=
41 net::registry_controlled_domains::GetRegistryLength(
43 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES
,
44 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES
);
45 // If there was more than just a TLD in the host (e.g., *.foobar.com), it
46 // doesn't imply all hosts.
47 if (registry_length
> 0)
50 // At this point the host could either be just a TLD ("com") or some unknown
51 // TLD-like string ("notatld"). To disambiguate between them construct a
52 // fake URL, and check the registry. This returns 0 if the TLD is
53 // unrecognized, or the length of the recognized TLD.
54 registry_length
= net::registry_controlled_domains::GetRegistryLength(
55 base::StringPrintf("foo.%s", iter
->host().c_str()),
56 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES
,
57 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES
);
58 // If we recognized this TLD, then this is a pattern like *.com, and it
59 // should imply all hosts.
60 if (registry_length
> 0)
68 typename
T::iterator
FindMessageByID(T
& messages
, int id
) {
69 for (typename
T::iterator it
= messages
.begin();
70 it
!= messages
.end(); ++it
) {
74 return messages
.end();
78 void SuppressMessage(T
& messages
,
79 int suppressing_message
,
80 int suppressed_message
) {
81 typename
T::iterator suppressed
= FindMessageByID(messages
,
83 if (suppressed
!= messages
.end() &&
84 FindMessageByID(messages
, suppressing_message
) != messages
.end()) {
85 messages
.erase(suppressed
);
91 ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
94 ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
97 PermissionMessages
ChromePermissionMessageProvider::GetPermissionMessages(
98 const PermissionSet
* permissions
,
99 Manifest::Type extension_type
) const {
100 PermissionMessages messages
;
102 if (permissions
->HasEffectiveFullAccess()) {
103 messages
.push_back(PermissionMessage(
104 PermissionMessage::kFullAccess
,
105 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS
)));
109 PermissionMsgSet host_msgs
=
110 GetHostPermissionMessages(permissions
, extension_type
);
111 PermissionMsgSet api_msgs
= GetAPIPermissionMessages(permissions
);
112 PermissionMsgSet manifest_permission_msgs
=
113 GetManifestPermissionMessages(permissions
);
114 messages
.insert(messages
.end(), host_msgs
.begin(), host_msgs
.end());
115 messages
.insert(messages
.end(), api_msgs
.begin(), api_msgs
.end());
116 messages
.insert(messages
.end(), manifest_permission_msgs
.begin(),
117 manifest_permission_msgs
.end());
119 // Some warnings are more generic and/or powerful and superseed other
120 // warnings. In that case, suppress the superseeded warning.
121 SuppressMessage(messages
,
122 PermissionMessage::kBookmarks
,
123 PermissionMessage::kOverrideBookmarksUI
);
124 // Both tabs and history already allow reading favicons.
125 SuppressMessage(messages
,
126 PermissionMessage::kTabs
,
127 PermissionMessage::kFavicon
);
128 SuppressMessage(messages
,
129 PermissionMessage::kBrowsingHistory
,
130 PermissionMessage::kFavicon
);
131 // Warning for history permission already covers warning for tabs permission.
132 SuppressMessage(messages
,
133 PermissionMessage::kBrowsingHistory
,
134 PermissionMessage::kTabs
);
138 std::vector
<base::string16
> ChromePermissionMessageProvider::GetWarningMessages(
139 const PermissionSet
* permissions
,
140 Manifest::Type extension_type
) const {
141 std::vector
<base::string16
> message_strings
;
142 PermissionMessages messages
=
143 GetPermissionMessages(permissions
, extension_type
);
145 bool audio_capture
= false;
146 bool video_capture
= false;
147 bool media_galleries_read
= false;
148 bool media_galleries_copy_to
= false;
149 bool media_galleries_delete
= false;
150 for (PermissionMessages::const_iterator i
= messages
.begin();
151 i
!= messages
.end(); ++i
) {
153 case PermissionMessage::kAudioCapture
:
154 audio_capture
= true;
156 case PermissionMessage::kVideoCapture
:
157 video_capture
= true;
159 case PermissionMessage::kMediaGalleriesAllGalleriesRead
:
160 media_galleries_read
= true;
162 case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo
:
163 media_galleries_copy_to
= true;
165 case PermissionMessage::kMediaGalleriesAllGalleriesDelete
:
166 media_galleries_delete
= true;
173 for (PermissionMessages::const_iterator i
= messages
.begin();
174 i
!= messages
.end(); ++i
) {
176 if (audio_capture
&& video_capture
) {
177 if (id
== PermissionMessage::kAudioCapture
) {
178 message_strings
.push_back(l10n_util::GetStringUTF16(
179 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE
));
181 } else if (id
== PermissionMessage::kVideoCapture
) {
182 // The combined message will be pushed above.
186 if (media_galleries_read
&&
187 (media_galleries_copy_to
|| media_galleries_delete
)) {
188 if (id
== PermissionMessage::kMediaGalleriesAllGalleriesRead
) {
189 int m_id
= media_galleries_copy_to
?
190 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE
:
191 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_DELETE
;
192 message_strings
.push_back(l10n_util::GetStringUTF16(m_id
));
194 } else if (id
== PermissionMessage::kMediaGalleriesAllGalleriesCopyTo
||
195 id
== PermissionMessage::kMediaGalleriesAllGalleriesDelete
) {
196 // The combined message will be pushed above.
201 message_strings
.push_back(i
->message());
204 return message_strings
;
207 std::vector
<base::string16
>
208 ChromePermissionMessageProvider::GetWarningMessagesDetails(
209 const PermissionSet
* permissions
,
210 Manifest::Type extension_type
) const {
211 std::vector
<base::string16
> message_strings
;
212 PermissionMessages messages
=
213 GetPermissionMessages(permissions
, extension_type
);
215 for (PermissionMessages::const_iterator i
= messages
.begin();
216 i
!= messages
.end(); ++i
)
217 message_strings
.push_back(i
->details());
219 return message_strings
;
222 bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
223 const PermissionSet
* old_permissions
,
224 const PermissionSet
* new_permissions
,
225 Manifest::Type extension_type
) const {
226 // Things can't get worse than native code access.
227 if (old_permissions
->HasEffectiveFullAccess())
230 // Otherwise, it's a privilege increase if the new one has full access.
231 if (new_permissions
->HasEffectiveFullAccess())
234 if (IsHostPrivilegeIncrease(old_permissions
, new_permissions
, extension_type
))
237 if (IsAPIPrivilegeIncrease(old_permissions
, new_permissions
))
240 if (IsManifestPermissionPrivilegeIncrease(old_permissions
, new_permissions
))
246 std::set
<PermissionMessage
>
247 ChromePermissionMessageProvider::GetAPIPermissionMessages(
248 const PermissionSet
* permissions
) const {
249 PermissionMsgSet messages
;
250 for (APIPermissionSet::const_iterator permission_it
=
251 permissions
->apis().begin();
252 permission_it
!= permissions
->apis().end(); ++permission_it
) {
253 if (permission_it
->HasMessages()) {
254 PermissionMessages new_messages
= permission_it
->GetMessages();
255 messages
.insert(new_messages
.begin(), new_messages
.end());
259 // A special hack: If kFileSystemWriteDirectory would be displayed, hide
260 // kFileSystemDirectory as the write directory message implies it.
261 // TODO(sammc): Remove this. See http://crbug.com/284849.
262 SuppressMessage(messages
,
263 PermissionMessage::kFileSystemWriteDirectory
,
264 PermissionMessage::kFileSystemDirectory
);
265 // A special hack: The warning message for declarativeWebRequest
266 // permissions speaks about blocking parts of pages, which is a
267 // subset of what the "<all_urls>" access allows. Therefore we
268 // display only the "<all_urls>" warning message if both permissions
270 if (ShouldWarnAllHosts(permissions
)) {
273 PermissionMessage::kDeclarativeWebRequest
, base::string16()));
278 std::set
<PermissionMessage
>
279 ChromePermissionMessageProvider::GetManifestPermissionMessages(
280 const PermissionSet
* permissions
) const {
281 PermissionMsgSet messages
;
282 for (ManifestPermissionSet::const_iterator permission_it
=
283 permissions
->manifest_permissions().begin();
284 permission_it
!= permissions
->manifest_permissions().end();
286 if (permission_it
->HasMessages()) {
287 PermissionMessages new_messages
= permission_it
->GetMessages();
288 messages
.insert(new_messages
.begin(), new_messages
.end());
294 std::set
<PermissionMessage
>
295 ChromePermissionMessageProvider::GetHostPermissionMessages(
296 const PermissionSet
* permissions
,
297 Manifest::Type extension_type
) const {
298 PermissionMsgSet messages
;
299 // Since platform apps always use isolated storage, they can't (silently)
300 // access user data on other domains, so there's no need to prompt.
301 // Note: this must remain consistent with IsHostPrivilegeIncrease.
302 // See crbug.com/255229.
303 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
306 if (ShouldWarnAllHosts(permissions
)) {
307 messages
.insert(PermissionMessage(
308 PermissionMessage::kHostsAll
,
309 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS
)));
311 URLPatternSet regular_hosts
;
312 ExtensionsClient::Get()->FilterHostPermissions(
313 permissions
->effective_hosts(), ®ular_hosts
, &messages
);
315 std::set
<std::string
> hosts
=
316 permission_message_util::GetDistinctHosts(regular_hosts
, true, true);
318 messages
.insert(permission_message_util::CreateFromHostList(hosts
));
323 bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease(
324 const PermissionSet
* old_permissions
,
325 const PermissionSet
* new_permissions
) const {
326 if (new_permissions
== NULL
)
329 PermissionMsgSet old_warnings
= GetAPIPermissionMessages(old_permissions
);
330 PermissionMsgSet new_warnings
= GetAPIPermissionMessages(new_permissions
);
331 PermissionMsgSet delta_warnings
=
332 base::STLSetDifference
<PermissionMsgSet
>(new_warnings
, old_warnings
);
334 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory.
335 // TODO(sammc): Remove this. See http://crbug.com/284849.
336 if (old_warnings
.find(PermissionMessage(
337 PermissionMessage::kFileSystemWriteDirectory
, base::string16())) !=
338 old_warnings
.end()) {
339 delta_warnings
.erase(
340 PermissionMessage(PermissionMessage::kFileSystemDirectory
,
344 // It is a privilege increase if there are additional warnings present.
345 return !delta_warnings
.empty();
348 bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease(
349 const PermissionSet
* old_permissions
,
350 const PermissionSet
* new_permissions
) const {
351 if (new_permissions
== NULL
)
354 PermissionMsgSet old_warnings
=
355 GetManifestPermissionMessages(old_permissions
);
356 PermissionMsgSet new_warnings
=
357 GetManifestPermissionMessages(new_permissions
);
358 PermissionMsgSet delta_warnings
=
359 base::STLSetDifference
<PermissionMsgSet
>(new_warnings
, old_warnings
);
361 // It is a privilege increase if there are additional warnings present.
362 return !delta_warnings
.empty();
365 bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
366 const PermissionSet
* old_permissions
,
367 const PermissionSet
* new_permissions
,
368 Manifest::Type extension_type
) const {
369 // Platform apps host permission changes do not count as privilege increases.
370 // Note: this must remain consistent with GetHostPermissionMessages.
371 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
374 // If the old permission set can access any host, then it can't be elevated.
375 if (old_permissions
->HasEffectiveAccessToAllHosts())
378 // Likewise, if the new permission set has full host access, then it must be
379 // a privilege increase.
380 if (new_permissions
->HasEffectiveAccessToAllHosts())
383 const URLPatternSet
& old_list
= old_permissions
->effective_hosts();
384 const URLPatternSet
& new_list
= new_permissions
->effective_hosts();
386 // TODO(jstritar): This is overly conservative with respect to subdomains.
387 // For example, going from *.google.com to www.google.com will be
388 // considered an elevation, even though it is not (http://crbug.com/65337).
389 std::set
<std::string
> new_hosts_set(
390 permission_message_util::GetDistinctHosts(new_list
, false, false));
391 std::set
<std::string
> old_hosts_set(
392 permission_message_util::GetDistinctHosts(old_list
, false, false));
393 std::set
<std::string
> new_hosts_only
=
394 base::STLSetDifference
<std::set
<std::string
> >(new_hosts_set
,
397 return !new_hosts_only
.empty();
400 } // namespace extensions