1 // Copyright (c) 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/permission_set.h"
11 #include "base/stl_util.h"
12 #include "chrome/common/extensions/permissions/chrome_scheme_hosts.h"
13 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
14 #include "chrome/common/extensions/permissions/permissions_info.h"
15 #include "content/public/common/url_constants.h"
16 #include "extensions/common/url_pattern.h"
17 #include "extensions/common/url_pattern_set.h"
18 #include "grit/generated_resources.h"
19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
20 #include "ui/base/l10n/l10n_util.h"
23 using extensions::URLPatternSet
;
27 // Helper for GetDistinctHosts(): com > net > org > everything else.
28 bool RcdBetterThan(const std::string
& a
, const std::string
& b
) {
36 return b
!= "com" && b
!= "net";
40 void AddPatternsAndRemovePaths(const URLPatternSet
& set
, URLPatternSet
* out
) {
42 for (URLPatternSet::const_iterator i
= set
.begin(); i
!= set
.end(); ++i
) {
51 namespace extensions
{
57 PermissionSet::PermissionSet() {}
59 PermissionSet::PermissionSet(
60 const APIPermissionSet
& apis
,
61 const URLPatternSet
& explicit_hosts
,
62 const URLPatternSet
& scriptable_hosts
)
64 scriptable_hosts_(scriptable_hosts
) {
65 AddPatternsAndRemovePaths(explicit_hosts
, &explicit_hosts_
);
66 InitImplicitPermissions();
71 PermissionSet
* PermissionSet::CreateDifference(
72 const PermissionSet
* set1
,
73 const PermissionSet
* set2
) {
74 scoped_refptr
<PermissionSet
> empty
= new PermissionSet();
75 const PermissionSet
* set1_safe
= (set1
== NULL
) ? empty
.get() : set1
;
76 const PermissionSet
* set2_safe
= (set2
== NULL
) ? empty
.get() : set2
;
78 APIPermissionSet apis
;
79 APIPermissionSet::Difference(set1_safe
->apis(), set2_safe
->apis(), &apis
);
81 URLPatternSet explicit_hosts
;
82 URLPatternSet::CreateDifference(set1_safe
->explicit_hosts(),
83 set2_safe
->explicit_hosts(),
86 URLPatternSet scriptable_hosts
;
87 URLPatternSet::CreateDifference(set1_safe
->scriptable_hosts(),
88 set2_safe
->scriptable_hosts(),
91 return new PermissionSet(apis
, explicit_hosts
, scriptable_hosts
);
95 PermissionSet
* PermissionSet::CreateIntersection(
96 const PermissionSet
* set1
,
97 const PermissionSet
* set2
) {
98 scoped_refptr
<PermissionSet
> empty
= new PermissionSet();
99 const PermissionSet
* set1_safe
= (set1
== NULL
) ? empty
.get() : set1
;
100 const PermissionSet
* set2_safe
= (set2
== NULL
) ? empty
.get() : set2
;
102 APIPermissionSet apis
;
103 APIPermissionSet::Intersection(set1_safe
->apis(), set2_safe
->apis(), &apis
);
105 URLPatternSet explicit_hosts
;
106 URLPatternSet::CreateIntersection(set1_safe
->explicit_hosts(),
107 set2_safe
->explicit_hosts(),
110 URLPatternSet scriptable_hosts
;
111 URLPatternSet::CreateIntersection(set1_safe
->scriptable_hosts(),
112 set2_safe
->scriptable_hosts(),
115 return new PermissionSet(apis
, explicit_hosts
, scriptable_hosts
);
119 PermissionSet
* PermissionSet::CreateUnion(
120 const PermissionSet
* set1
,
121 const PermissionSet
* set2
) {
122 scoped_refptr
<PermissionSet
> empty
= new PermissionSet();
123 const PermissionSet
* set1_safe
= (set1
== NULL
) ? empty
.get() : set1
;
124 const PermissionSet
* set2_safe
= (set2
== NULL
) ? empty
.get() : set2
;
126 APIPermissionSet apis
;
127 APIPermissionSet::Union(set1_safe
->apis(), set2_safe
->apis(), &apis
);
129 URLPatternSet explicit_hosts
;
130 URLPatternSet::CreateUnion(set1_safe
->explicit_hosts(),
131 set2_safe
->explicit_hosts(),
134 URLPatternSet scriptable_hosts
;
135 URLPatternSet::CreateUnion(set1_safe
->scriptable_hosts(),
136 set2_safe
->scriptable_hosts(),
139 return new PermissionSet(apis
, explicit_hosts
, scriptable_hosts
);
143 PermissionSet
* PermissionSet::ExcludeNotInManifestPermissions(
144 const PermissionSet
* set
) {
146 return new PermissionSet();
148 APIPermissionSet apis
;
149 for (APIPermissionSet::const_iterator i
= set
->apis().begin();
150 i
!= set
->apis().end(); ++i
) {
151 if (!i
->ManifestEntryForbidden())
152 apis
.insert(i
->Clone());
155 return new PermissionSet(
156 apis
, set
->explicit_hosts(), set
->scriptable_hosts());
159 bool PermissionSet::operator==(
160 const PermissionSet
& rhs
) const {
161 return apis_
== rhs
.apis_
&&
162 scriptable_hosts_
== rhs
.scriptable_hosts_
&&
163 explicit_hosts_
== rhs
.explicit_hosts_
;
166 bool PermissionSet::Contains(const PermissionSet
& set
) const {
167 return apis_
.Contains(set
.apis()) &&
168 explicit_hosts().Contains(set
.explicit_hosts()) &&
169 scriptable_hosts().Contains(set
.scriptable_hosts());
172 std::set
<std::string
> PermissionSet::GetAPIsAsStrings() const {
173 std::set
<std::string
> apis_str
;
174 for (APIPermissionSet::const_iterator i
= apis_
.begin();
175 i
!= apis_
.end(); ++i
) {
176 apis_str
.insert(i
->name());
181 std::set
<std::string
> PermissionSet::GetDistinctHostsForDisplay() const {
182 URLPatternSet hosts_displayed_as_url
;
183 // Filters out every URL pattern that matches chrome:// scheme.
184 for (URLPatternSet::const_iterator i
= effective_hosts_
.begin();
185 i
!= effective_hosts_
.end(); ++i
) {
186 if (i
->scheme() != chrome::kChromeUIScheme
) {
187 hosts_displayed_as_url
.AddPattern(*i
);
190 return GetDistinctHosts(hosts_displayed_as_url
, true, true);
193 PermissionMessages
PermissionSet::GetPermissionMessages(
194 Manifest::Type extension_type
) const {
195 PermissionMessages messages
;
197 if (HasEffectiveFullAccess()) {
198 messages
.push_back(PermissionMessage(
199 PermissionMessage::kFullAccess
,
200 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS
)));
204 std::set
<PermissionMessage
> host_msgs
=
205 GetHostPermissionMessages(extension_type
);
206 std::set
<PermissionMessage
> api_msgs
= GetAPIPermissionMessages();
207 messages
.insert(messages
.end(), host_msgs
.begin(), host_msgs
.end());
208 messages
.insert(messages
.end(), api_msgs
.begin(), api_msgs
.end());
213 std::vector
<string16
> PermissionSet::GetWarningMessages(
214 Manifest::Type extension_type
) const {
215 std::vector
<string16
> messages
;
216 PermissionMessages permissions
= GetPermissionMessages(extension_type
);
218 bool audio_capture
= false;
219 bool video_capture
= false;
220 bool media_galleries_read
= false;
221 bool media_galleries_copy_to
= false;
222 for (PermissionMessages::const_iterator i
= permissions
.begin();
223 i
!= permissions
.end(); ++i
) {
225 case PermissionMessage::kAudioCapture
:
226 audio_capture
= true;
228 case PermissionMessage::kVideoCapture
:
229 video_capture
= true;
231 case PermissionMessage::kMediaGalleriesAllGalleriesRead
:
232 media_galleries_read
= true;
234 case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo
:
235 media_galleries_copy_to
= true;
242 for (PermissionMessages::const_iterator i
= permissions
.begin();
243 i
!= permissions
.end(); ++i
) {
245 if (audio_capture
&& video_capture
) {
246 if (id
== PermissionMessage::kAudioCapture
) {
247 messages
.push_back(l10n_util::GetStringUTF16(
248 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE
));
250 } else if (id
== PermissionMessage::kVideoCapture
) {
251 // The combined message will be pushed above.
255 if (media_galleries_read
&& media_galleries_copy_to
) {
256 if (id
== PermissionMessage::kMediaGalleriesAllGalleriesRead
) {
257 messages
.push_back(l10n_util::GetStringUTF16(
258 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE
));
260 } else if (id
== PermissionMessage::kMediaGalleriesAllGalleriesCopyTo
) {
261 // The combined message will be pushed above.
266 // The warning message for declarativeWebRequest permissions speaks about
267 // blocking parts of pages, which is a subset of what the "<all_urls>"
268 // access allows. Therefore we display only the "<all_urls>" warning message
269 // if both permissions are required.
270 if (id
== PermissionMessage::kDeclarativeWebRequest
&&
271 HasEffectiveAccessToAllHosts())
274 messages
.push_back(i
->message());
280 std::vector
<string16
> PermissionSet::GetWarningMessagesDetails(
281 Manifest::Type extension_type
) const {
282 std::vector
<string16
> messages
;
283 PermissionMessages permissions
= GetPermissionMessages(extension_type
);
285 for (PermissionMessages::const_iterator i
= permissions
.begin();
286 i
!= permissions
.end(); ++i
)
287 messages
.push_back(i
->details());
292 bool PermissionSet::IsEmpty() const {
293 // Not default if any host permissions are present.
294 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
297 // Or if it has no api permissions.
298 return apis().empty();
301 bool PermissionSet::HasAPIPermission(
302 APIPermission::ID id
) const {
303 return apis().find(id
) != apis().end();
306 bool PermissionSet::HasAPIPermission(const std::string
& permission_name
) const {
307 const APIPermissionInfo
* permission
=
308 PermissionsInfo::GetInstance()->GetByName(permission_name
);
309 CHECK(permission
) << permission_name
;
310 return (permission
&& apis_
.count(permission
->id()));
313 bool PermissionSet::CheckAPIPermission(APIPermission::ID permission
) const {
314 return CheckAPIPermissionWithParam(permission
, NULL
);
317 bool PermissionSet::CheckAPIPermissionWithParam(
318 APIPermission::ID permission
,
319 const APIPermission::CheckParam
* param
) const {
320 APIPermissionSet::const_iterator iter
= apis().find(permission
);
321 if (iter
== apis().end())
323 return iter
->Check(param
);
326 bool PermissionSet::HasExplicitAccessToOrigin(
327 const GURL
& origin
) const {
328 return explicit_hosts().MatchesURL(origin
);
331 bool PermissionSet::HasScriptableAccessToURL(
332 const GURL
& origin
) const {
333 // We only need to check our host list to verify access. The host list should
334 // already reflect any special rules (such as chrome://favicon, all hosts
336 return scriptable_hosts().MatchesURL(origin
);
339 bool PermissionSet::HasEffectiveAccessToAllHosts() const {
340 // There are two ways this set can have effective access to all hosts:
341 // 1) it has an <all_urls> URL pattern.
342 // 2) it has a named permission with implied full URL access.
343 for (URLPatternSet::const_iterator host
= effective_hosts().begin();
344 host
!= effective_hosts().end(); ++host
) {
345 if (host
->match_all_urls() ||
346 (host
->match_subdomains() && host
->host().empty()))
350 for (APIPermissionSet::const_iterator i
= apis().begin();
351 i
!= apis().end(); ++i
) {
352 if (i
->info()->implies_full_url_access())
358 bool PermissionSet::HasEffectiveAccessToURL(const GURL
& url
) const {
359 return effective_hosts().MatchesURL(url
);
362 bool PermissionSet::HasEffectiveFullAccess() const {
363 for (APIPermissionSet::const_iterator i
= apis().begin();
364 i
!= apis().end(); ++i
) {
365 if (i
->info()->implies_full_access())
371 bool PermissionSet::HasLessPrivilegesThan(
372 const PermissionSet
* permissions
,
373 Manifest::Type extension_type
) const {
374 // Things can't get worse than native code access.
375 if (HasEffectiveFullAccess())
378 // Otherwise, it's a privilege increase if the new one has full access.
379 if (permissions
->HasEffectiveFullAccess())
382 if (HasLessHostPrivilegesThan(permissions
, extension_type
))
385 if (HasLessAPIPrivilegesThan(permissions
))
391 PermissionSet::~PermissionSet() {}
394 std::set
<std::string
> PermissionSet::GetDistinctHosts(
395 const URLPatternSet
& host_patterns
,
397 bool exclude_file_scheme
) {
398 // Use a vector to preserve order (also faster than a map on small sets).
399 // Each item is a host split into two parts: host without RCDs and
401 typedef std::vector
<std::pair
<std::string
, std::string
> > HostVector
;
402 HostVector hosts_best_rcd
;
403 for (URLPatternSet::const_iterator i
= host_patterns
.begin();
404 i
!= host_patterns
.end(); ++i
) {
405 if (exclude_file_scheme
&& i
->scheme() == chrome::kFileScheme
)
408 std::string host
= i
->host();
410 // Add the subdomain wildcard back to the host, if necessary.
411 if (i
->match_subdomains())
414 // If the host has an RCD, split it off so we can detect duplicates.
416 size_t reg_len
= net::registry_controlled_domains::GetRegistryLength(
418 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES
,
419 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES
);
420 if (reg_len
&& reg_len
!= std::string::npos
) {
421 if (include_rcd
) // else leave rcd empty
422 rcd
= host
.substr(host
.size() - reg_len
);
423 host
= host
.substr(0, host
.size() - reg_len
);
426 // Check if we've already seen this host.
427 HostVector::iterator it
= hosts_best_rcd
.begin();
428 for (; it
!= hosts_best_rcd
.end(); ++it
) {
429 if (it
->first
== host
)
432 // If this host was found, replace the RCD if this one is better.
433 if (it
!= hosts_best_rcd
.end()) {
434 if (include_rcd
&& RcdBetterThan(rcd
, it
->second
))
436 } else { // Previously unseen host, append it.
437 hosts_best_rcd
.push_back(std::make_pair(host
, rcd
));
441 // Build up the final vector by concatenating hosts and RCDs.
442 std::set
<std::string
> distinct_hosts
;
443 for (HostVector::iterator it
= hosts_best_rcd
.begin();
444 it
!= hosts_best_rcd
.end(); ++it
)
445 distinct_hosts
.insert(it
->first
+ it
->second
);
446 return distinct_hosts
;
449 void PermissionSet::InitImplicitPermissions() {
450 // The downloads permission implies the internal version as well.
451 if (apis_
.find(APIPermission::kDownloads
) != apis_
.end())
452 apis_
.insert(APIPermission::kDownloadsInternal
);
454 // TODO(fsamuel): Is there a better way to request access to the WebRequest
455 // API without exposing it to the Chrome App?
456 if (apis_
.find(APIPermission::kWebView
) != apis_
.end())
457 apis_
.insert(APIPermission::kWebRequestInternal
);
459 // The webRequest permission implies the internal version as well.
460 if (apis_
.find(APIPermission::kWebRequest
) != apis_
.end())
461 apis_
.insert(APIPermission::kWebRequestInternal
);
463 // The fileBrowserHandler permission implies the internal version as well.
464 if (apis_
.find(APIPermission::kFileBrowserHandler
) != apis_
.end())
465 apis_
.insert(APIPermission::kFileBrowserHandlerInternal
);
468 void PermissionSet::InitEffectiveHosts() {
469 effective_hosts_
.ClearPatterns();
471 URLPatternSet::CreateUnion(
472 explicit_hosts(), scriptable_hosts(), &effective_hosts_
);
475 std::set
<PermissionMessage
> PermissionSet::GetAPIPermissionMessages() const {
476 std::set
<PermissionMessage
> messages
;
477 for (APIPermissionSet::const_iterator permission_it
= apis_
.begin();
478 permission_it
!= apis_
.end(); ++permission_it
) {
479 if (permission_it
->HasMessages()) {
480 PermissionMessages new_messages
= permission_it
->GetMessages();
481 messages
.insert(new_messages
.begin(), new_messages
.end());
485 // A special hack: If kFileSystemWriteDirectory would be displayed, hide
486 // kFileSystemDirectory and and kFileSystemWrite as the write directory
487 // message implies the other two.
488 // TODO(sammc): Remove this. See http://crbug.com/284849.
489 std::set
<PermissionMessage
>::iterator write_directory_message
=
490 messages
.find(PermissionMessage(
491 PermissionMessage::kFileSystemWriteDirectory
, string16()));
492 if (write_directory_message
!= messages
.end()) {
494 PermissionMessage(PermissionMessage::kFileSystemWrite
, string16()));
496 PermissionMessage(PermissionMessage::kFileSystemDirectory
, string16()));
501 std::set
<PermissionMessage
> PermissionSet::GetHostPermissionMessages(
502 Manifest::Type extension_type
) const {
503 // Since platform apps always use isolated storage, they can't (silently)
504 // access user data on other domains, so there's no need to prompt.
505 // Note: this must remain consistent with HasLessHostPrivilegesThan.
506 // See crbug.com/255229.
507 std::set
<PermissionMessage
> messages
;
508 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
511 if (HasEffectiveAccessToAllHosts()) {
512 messages
.insert(PermissionMessage(
513 PermissionMessage::kHostsAll
,
514 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS
)));
516 PermissionMessages additional_warnings
=
517 GetChromeSchemePermissionWarnings(effective_hosts_
);
518 for (size_t i
= 0; i
< additional_warnings
.size(); ++i
)
519 messages
.insert(additional_warnings
[i
]);
521 std::set
<std::string
> hosts
= GetDistinctHostsForDisplay();
523 messages
.insert(PermissionMessage::CreateFromHostList(hosts
));
528 bool PermissionSet::HasLessAPIPrivilegesThan(
529 const PermissionSet
* permissions
) const {
530 if (permissions
== NULL
)
533 typedef std::set
<PermissionMessage
> PermissionMsgSet
;
534 PermissionMsgSet current_warnings
= GetAPIPermissionMessages();
535 PermissionMsgSet new_warnings
= permissions
->GetAPIPermissionMessages();
536 PermissionMsgSet delta_warnings
=
537 base::STLSetDifference
<PermissionMsgSet
>(new_warnings
, current_warnings
);
539 // A special hack: the DWR permission is weaker than all hosts permission.
540 if (delta_warnings
.size() == 1u &&
541 delta_warnings
.begin()->id() ==
542 PermissionMessage::kDeclarativeWebRequest
&&
543 HasEffectiveAccessToAllHosts()) {
547 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and
549 // TODO(sammc): Remove this. See http://crbug.com/284849.
550 if (current_warnings
.find(PermissionMessage(
551 PermissionMessage::kFileSystemWriteDirectory
, string16())) !=
552 current_warnings
.end()) {
553 delta_warnings
.erase(
554 PermissionMessage(PermissionMessage::kFileSystemDirectory
, string16()));
555 delta_warnings
.erase(
556 PermissionMessage(PermissionMessage::kFileSystemWrite
, string16()));
559 // We have less privileges if there are additional warnings present.
560 return !delta_warnings
.empty();
563 bool PermissionSet::HasLessHostPrivilegesThan(
564 const PermissionSet
* permissions
,
565 Manifest::Type extension_type
) const {
566 // Platform apps host permission changes do not count as privilege increases.
567 // Note: this must remain consistent with GetHostPermissionMessages.
568 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
571 // If this permission set can access any host, then it can't be elevated.
572 if (HasEffectiveAccessToAllHosts())
575 // Likewise, if the other permission set has full host access, then it must be
576 // a privilege increase.
577 if (permissions
->HasEffectiveAccessToAllHosts())
580 const URLPatternSet
& old_list
= effective_hosts();
581 const URLPatternSet
& new_list
= permissions
->effective_hosts();
583 // TODO(jstritar): This is overly conservative with respect to subdomains.
584 // For example, going from *.google.com to www.google.com will be
585 // considered an elevation, even though it is not (http://crbug.com/65337).
586 std::set
<std::string
> new_hosts_set(GetDistinctHosts(new_list
, false, false));
587 std::set
<std::string
> old_hosts_set(GetDistinctHosts(old_list
, false, false));
588 std::set
<std::string
> new_hosts_only
=
589 base::STLSetDifference
<std::set
<std::string
> >(new_hosts_set
,
592 return !new_hosts_only
.empty();
595 } // namespace extensions