Require confirmation for writable directory access.
[chromium-blink-merge.git] / chrome / common / extensions / permissions / permission_set.cc
blob455ddd33af76532de178371ac6b34f5a352298a2
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"
7 #include <algorithm>
8 #include <iterator>
9 #include <string>
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"
21 #include "url/gurl.h"
23 using extensions::URLPatternSet;
25 namespace {
27 // Helper for GetDistinctHosts(): com > net > org > everything else.
28 bool RcdBetterThan(const std::string& a, const std::string& b) {
29 if (a == b)
30 return false;
31 if (a == "com")
32 return true;
33 if (a == "net")
34 return b != "com";
35 if (a == "org")
36 return b != "com" && b != "net";
37 return false;
40 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
41 DCHECK(out);
42 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
43 URLPattern p = *i;
44 p.SetPath("/*");
45 out->AddPattern(p);
49 } // namespace
51 namespace extensions {
54 // PermissionSet
57 PermissionSet::PermissionSet() {}
59 PermissionSet::PermissionSet(
60 const APIPermissionSet& apis,
61 const URLPatternSet& explicit_hosts,
62 const URLPatternSet& scriptable_hosts)
63 : apis_(apis),
64 scriptable_hosts_(scriptable_hosts) {
65 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
66 InitImplicitPermissions();
67 InitEffectiveHosts();
70 // static
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(),
84 &explicit_hosts);
86 URLPatternSet scriptable_hosts;
87 URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
88 set2_safe->scriptable_hosts(),
89 &scriptable_hosts);
91 return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
94 // static
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(),
108 &explicit_hosts);
110 URLPatternSet scriptable_hosts;
111 URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
112 set2_safe->scriptable_hosts(),
113 &scriptable_hosts);
115 return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
118 // static
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(),
132 &explicit_hosts);
134 URLPatternSet scriptable_hosts;
135 URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
136 set2_safe->scriptable_hosts(),
137 &scriptable_hosts);
139 return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
142 // static
143 PermissionSet* PermissionSet::ExcludeNotInManifestPermissions(
144 const PermissionSet* set) {
145 if (!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());
178 return apis_str;
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)));
201 return messages;
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());
210 return messages;
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) {
224 switch (i->id()) {
225 case PermissionMessage::kAudioCapture:
226 audio_capture = true;
227 break;
228 case PermissionMessage::kVideoCapture:
229 video_capture = true;
230 break;
231 case PermissionMessage::kMediaGalleriesAllGalleriesRead:
232 media_galleries_read = true;
233 break;
234 case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo:
235 media_galleries_copy_to = true;
236 break;
237 default:
238 break;
242 for (PermissionMessages::const_iterator i = permissions.begin();
243 i != permissions.end(); ++i) {
244 int id = i->id();
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));
249 continue;
250 } else if (id == PermissionMessage::kVideoCapture) {
251 // The combined message will be pushed above.
252 continue;
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));
259 continue;
260 } else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) {
261 // The combined message will be pushed above.
262 continue;
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())
272 continue;
274 messages.push_back(i->message());
277 return messages;
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());
289 return messages;
292 bool PermissionSet::IsEmpty() const {
293 // Not default if any host permissions are present.
294 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
295 return false;
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())
322 return false;
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
335 // access, etc.).
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()))
347 return true;
350 for (APIPermissionSet::const_iterator i = apis().begin();
351 i != apis().end(); ++i) {
352 if (i->info()->implies_full_url_access())
353 return true;
355 return false;
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())
366 return true;
368 return false;
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())
376 return false;
378 // Otherwise, it's a privilege increase if the new one has full access.
379 if (permissions->HasEffectiveFullAccess())
380 return true;
382 if (HasLessHostPrivilegesThan(permissions, extension_type))
383 return true;
385 if (HasLessAPIPrivilegesThan(permissions))
386 return true;
388 return false;
391 PermissionSet::~PermissionSet() {}
393 // static
394 std::set<std::string> PermissionSet::GetDistinctHosts(
395 const URLPatternSet& host_patterns,
396 bool include_rcd,
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
400 // current best RCD.
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)
406 continue;
408 std::string host = i->host();
410 // Add the subdomain wildcard back to the host, if necessary.
411 if (i->match_subdomains())
412 host = "*." + host;
414 // If the host has an RCD, split it off so we can detect duplicates.
415 std::string rcd;
416 size_t reg_len = net::registry_controlled_domains::GetRegistryLength(
417 host,
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)
430 break;
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))
435 it->second = rcd;
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()) {
493 messages.erase(
494 PermissionMessage(PermissionMessage::kFileSystemWrite, string16()));
495 messages.erase(
496 PermissionMessage(PermissionMessage::kFileSystemDirectory, string16()));
498 return messages;
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)
509 return messages;
511 if (HasEffectiveAccessToAllHosts()) {
512 messages.insert(PermissionMessage(
513 PermissionMessage::kHostsAll,
514 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
515 } else {
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();
522 if (!hosts.empty())
523 messages.insert(PermissionMessage::CreateFromHostList(hosts));
525 return messages;
528 bool PermissionSet::HasLessAPIPrivilegesThan(
529 const PermissionSet* permissions) const {
530 if (permissions == NULL)
531 return false;
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()) {
544 return false;
547 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and
548 // kFileSystemWrite.
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)
569 return false;
571 // If this permission set can access any host, then it can't be elevated.
572 if (HasEffectiveAccessToAllHosts())
573 return false;
575 // Likewise, if the other permission set has full host access, then it must be
576 // a privilege increase.
577 if (permissions->HasEffectiveAccessToAllHosts())
578 return true;
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,
590 old_hosts_set);
592 return !new_hosts_only.empty();
595 } // namespace extensions