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. 4 5 #include "chrome/common/extensions/permissions/permission_set.h" 6 7 #include <algorithm> 8 #include <iterator> 9 #include <string> 10 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" 22 23 using extensions::URLPatternSet; 24 25 namespace { 26 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; 38 } 39 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); 46 } 47 } 48 49 } // namespace 50 51 namespace extensions { 52 53 // 54 // PermissionSet 55 // 56 57 PermissionSet::PermissionSet() {} 58 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(); 68 } 69 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; 77 78 APIPermissionSet apis; 79 APIPermissionSet::Difference(set1_safe->apis(), set2_safe->apis(), &apis); 80 81 URLPatternSet explicit_hosts; 82 URLPatternSet::CreateDifference(set1_safe->explicit_hosts(), 83 set2_safe->explicit_hosts(), 84 &explicit_hosts); 85 86 URLPatternSet scriptable_hosts; 87 URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(), 88 set2_safe->scriptable_hosts(), 89 &scriptable_hosts); 90 91 return new PermissionSet(apis, explicit_hosts, scriptable_hosts); 92 } 93 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; 101 102 APIPermissionSet apis; 103 APIPermissionSet::Intersection(set1_safe->apis(), set2_safe->apis(), &apis); 104 105 URLPatternSet explicit_hosts; 106 URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(), 107 set2_safe->explicit_hosts(), 108 &explicit_hosts); 109 110 URLPatternSet scriptable_hosts; 111 URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(), 112 set2_safe->scriptable_hosts(), 113 &scriptable_hosts); 114 115 return new PermissionSet(apis, explicit_hosts, scriptable_hosts); 116 } 117 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; 125 126 APIPermissionSet apis; 127 APIPermissionSet::Union(set1_safe->apis(), set2_safe->apis(), &apis); 128 129 URLPatternSet explicit_hosts; 130 URLPatternSet::CreateUnion(set1_safe->explicit_hosts(), 131 set2_safe->explicit_hosts(), 132 &explicit_hosts); 133 134 URLPatternSet scriptable_hosts; 135 URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(), 136 set2_safe->scriptable_hosts(), 137 &scriptable_hosts); 138 139 return new PermissionSet(apis, explicit_hosts, scriptable_hosts); 140 } 141 142 // static 143 PermissionSet* PermissionSet::ExcludeNotInManifestPermissions( 144 const PermissionSet* set) { 145 if (!set) 146 return new PermissionSet(); 147 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()); 153 } 154 155 return new PermissionSet( 156 apis, set->explicit_hosts(), set->scriptable_hosts()); 157 } 158 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_; 164 } 165 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()); 170 } 171 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()); 177 } 178 return apis_str; 179 } 180 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); 188 } 189 } 190 return GetDistinctHosts(hosts_displayed_as_url, true, true); 191 } 192 193 PermissionMessages PermissionSet::GetPermissionMessages( 194 Manifest::Type extension_type) const { 195 PermissionMessages messages; 196 197 if (HasEffectiveFullAccess()) { 198 messages.push_back(PermissionMessage( 199 PermissionMessage::kFullAccess, 200 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS))); 201 return messages; 202 } 203 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()); 209 210 return messages; 211 } 212 213 std::vector<string16> PermissionSet::GetWarningMessages( 214 Manifest::Type extension_type) const { 215 std::vector<string16> messages; 216 PermissionMessages permissions = GetPermissionMessages(extension_type); 217 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; 239 } 240 } 241 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; 253 } 254 } 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; 263 } 264 } 265 266 messages.push_back(i->message()); 267 } 268 269 return messages; 270 } 271 272 std::vector<string16> PermissionSet::GetWarningMessagesDetails( 273 Manifest::Type extension_type) const { 274 std::vector<string16> messages; 275 PermissionMessages permissions = GetPermissionMessages(extension_type); 276 277 for (PermissionMessages::const_iterator i = permissions.begin(); 278 i != permissions.end(); ++i) 279 messages.push_back(i->details()); 280 281 return messages; 282 } 283 284 bool PermissionSet::IsEmpty() const { 285 // Not default if any host permissions are present. 286 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) 287 return false; 288 289 // Or if it has no api permissions. 290 return apis().empty(); 291 } 292 293 bool PermissionSet::HasAPIPermission( 294 APIPermission::ID id) const { 295 return apis().find(id) != apis().end(); 296 } 297 298 bool PermissionSet::HasAPIPermission(const std::string& permission_name) const { 299 const APIPermissionInfo* permission = 300 PermissionsInfo::GetInstance()->GetByName(permission_name); 301 CHECK(permission) << permission_name; 302 return (permission && apis_.count(permission->id())); 303 } 304 305 bool PermissionSet::CheckAPIPermission(APIPermission::ID permission) const { 306 return CheckAPIPermissionWithParam(permission, NULL); 307 } 308 309 bool PermissionSet::CheckAPIPermissionWithParam( 310 APIPermission::ID permission, 311 const APIPermission::CheckParam* param) const { 312 APIPermissionSet::const_iterator iter = apis().find(permission); 313 if (iter == apis().end()) 314 return false; 315 return iter->Check(param); 316 } 317 318 bool PermissionSet::HasExplicitAccessToOrigin( 319 const GURL& origin) const { 320 return explicit_hosts().MatchesURL(origin); 321 } 322 323 bool PermissionSet::HasScriptableAccessToURL( 324 const GURL& origin) const { 325 // We only need to check our host list to verify access. The host list should 326 // already reflect any special rules (such as chrome://favicon, all hosts 327 // access, etc.). 328 return scriptable_hosts().MatchesURL(origin); 329 } 330 331 bool PermissionSet::HasEffectiveAccessToAllHosts() const { 332 // There are two ways this set can have effective access to all hosts: 333 // 1) it has an <all_urls> URL pattern. 334 // 2) it has a named permission with implied full URL access. 335 for (URLPatternSet::const_iterator host = effective_hosts().begin(); 336 host != effective_hosts().end(); ++host) { 337 if (host->match_all_urls() || 338 (host->match_subdomains() && host->host().empty())) 339 return true; 340 } 341 342 for (APIPermissionSet::const_iterator i = apis().begin(); 343 i != apis().end(); ++i) { 344 if (i->info()->implies_full_url_access()) 345 return true; 346 } 347 return false; 348 } 349 350 bool PermissionSet::HasEffectiveAccessToURL(const GURL& url) const { 351 return effective_hosts().MatchesURL(url); 352 } 353 354 bool PermissionSet::HasEffectiveFullAccess() const { 355 for (APIPermissionSet::const_iterator i = apis().begin(); 356 i != apis().end(); ++i) { 357 if (i->info()->implies_full_access()) 358 return true; 359 } 360 return false; 361 } 362 363 bool PermissionSet::HasLessPrivilegesThan( 364 const PermissionSet* permissions, 365 Manifest::Type extension_type) const { 366 // Things can't get worse than native code access. 367 if (HasEffectiveFullAccess()) 368 return false; 369 370 // Otherwise, it's a privilege increase if the new one has full access. 371 if (permissions->HasEffectiveFullAccess()) 372 return true; 373 374 if (HasLessHostPrivilegesThan(permissions, extension_type)) 375 return true; 376 377 if (HasLessAPIPrivilegesThan(permissions)) 378 return true; 379 380 return false; 381 } 382 383 PermissionSet::~PermissionSet() {} 384 385 // static 386 std::set<std::string> PermissionSet::GetDistinctHosts( 387 const URLPatternSet& host_patterns, 388 bool include_rcd, 389 bool exclude_file_scheme) { 390 // Use a vector to preserve order (also faster than a map on small sets). 391 // Each item is a host split into two parts: host without RCDs and 392 // current best RCD. 393 typedef std::vector<std::pair<std::string, std::string> > HostVector; 394 HostVector hosts_best_rcd; 395 for (URLPatternSet::const_iterator i = host_patterns.begin(); 396 i != host_patterns.end(); ++i) { 397 if (exclude_file_scheme && i->scheme() == chrome::kFileScheme) 398 continue; 399 400 std::string host = i->host(); 401 402 // Add the subdomain wildcard back to the host, if necessary. 403 if (i->match_subdomains()) 404 host = "*." + host; 405 406 // If the host has an RCD, split it off so we can detect duplicates. 407 std::string rcd; 408 size_t reg_len = net::registry_controlled_domains::GetRegistryLength( 409 host, 410 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, 411 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 412 if (reg_len && reg_len != std::string::npos) { 413 if (include_rcd) // else leave rcd empty 414 rcd = host.substr(host.size() - reg_len); 415 host = host.substr(0, host.size() - reg_len); 416 } 417 418 // Check if we've already seen this host. 419 HostVector::iterator it = hosts_best_rcd.begin(); 420 for (; it != hosts_best_rcd.end(); ++it) { 421 if (it->first == host) 422 break; 423 } 424 // If this host was found, replace the RCD if this one is better. 425 if (it != hosts_best_rcd.end()) { 426 if (include_rcd && RcdBetterThan(rcd, it->second)) 427 it->second = rcd; 428 } else { // Previously unseen host, append it. 429 hosts_best_rcd.push_back(std::make_pair(host, rcd)); 430 } 431 } 432 433 // Build up the final vector by concatenating hosts and RCDs. 434 std::set<std::string> distinct_hosts; 435 for (HostVector::iterator it = hosts_best_rcd.begin(); 436 it != hosts_best_rcd.end(); ++it) 437 distinct_hosts.insert(it->first + it->second); 438 return distinct_hosts; 439 } 440 441 void PermissionSet::InitImplicitPermissions() { 442 // The downloads permission implies the internal version as well. 443 if (apis_.find(APIPermission::kDownloads) != apis_.end()) 444 apis_.insert(APIPermission::kDownloadsInternal); 445 446 // TODO(fsamuel): Is there a better way to request access to the WebRequest 447 // API without exposing it to the Chrome App? 448 if (apis_.find(APIPermission::kWebView) != apis_.end()) 449 apis_.insert(APIPermission::kWebRequestInternal); 450 451 // The webRequest permission implies the internal version as well. 452 if (apis_.find(APIPermission::kWebRequest) != apis_.end()) 453 apis_.insert(APIPermission::kWebRequestInternal); 454 455 // The fileBrowserHandler permission implies the internal version as well. 456 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end()) 457 apis_.insert(APIPermission::kFileBrowserHandlerInternal); 458 } 459 460 void PermissionSet::InitEffectiveHosts() { 461 effective_hosts_.ClearPatterns(); 462 463 URLPatternSet::CreateUnion( 464 explicit_hosts(), scriptable_hosts(), &effective_hosts_); 465 } 466 467 std::set<PermissionMessage> PermissionSet::GetAPIPermissionMessages() const { 468 std::set<PermissionMessage> messages; 469 for (APIPermissionSet::const_iterator permission_it = apis_.begin(); 470 permission_it != apis_.end(); ++permission_it) { 471 DCHECK_GT(PermissionMessage::kNone, 472 PermissionMessage::kUnknown); 473 if (permission_it->HasMessages()) { 474 PermissionMessages new_messages = permission_it->GetMessages(); 475 messages.insert(new_messages.begin(), new_messages.end()); 476 } 477 } 478 return messages; 479 } 480 481 std::set<PermissionMessage> PermissionSet::GetHostPermissionMessages( 482 Manifest::Type extension_type) const { 483 // Since platform apps always use isolated storage, they can't (silently) 484 // access user data on other domains, so there's no need to prompt. 485 // Note: this must remain consistent with HasLessHostPrivilegesThan. 486 // See crbug.com/255229. 487 std::set<PermissionMessage> messages; 488 if (extension_type == Manifest::TYPE_PLATFORM_APP) 489 return messages; 490 491 if (HasEffectiveAccessToAllHosts()) { 492 messages.insert(PermissionMessage( 493 PermissionMessage::kHostsAll, 494 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); 495 } else { 496 PermissionMessages additional_warnings = 497 GetChromeSchemePermissionWarnings(effective_hosts_); 498 for (size_t i = 0; i < additional_warnings.size(); ++i) 499 messages.insert(additional_warnings[i]); 500 501 std::set<std::string> hosts = GetDistinctHostsForDisplay(); 502 if (!hosts.empty()) 503 messages.insert(PermissionMessage::CreateFromHostList(hosts)); 504 } 505 return messages; 506 } 507 508 bool PermissionSet::HasLessAPIPrivilegesThan( 509 const PermissionSet* permissions) const { 510 if (permissions == NULL) 511 return false; 512 513 typedef std::set<PermissionMessage> PermissionMsgSet; 514 PermissionMsgSet current_warnings = GetAPIPermissionMessages(); 515 PermissionMsgSet new_warnings = permissions->GetAPIPermissionMessages(); 516 PermissionMsgSet delta_warnings = 517 base::STLSetDifference<PermissionMsgSet>(new_warnings, current_warnings); 518 519 // We have less privileges if there are additional warnings present. 520 return !delta_warnings.empty(); 521 } 522 523 bool PermissionSet::HasLessHostPrivilegesThan( 524 const PermissionSet* permissions, 525 Manifest::Type extension_type) const { 526 // Platform apps host permission changes do not count as privilege increases. 527 // Note: this must remain consistent with GetHostPermissionMessages. 528 if (extension_type == Manifest::TYPE_PLATFORM_APP) 529 return false; 530 531 // If this permission set can access any host, then it can't be elevated. 532 if (HasEffectiveAccessToAllHosts()) 533 return false; 534 535 // Likewise, if the other permission set has full host access, then it must be 536 // a privilege increase. 537 if (permissions->HasEffectiveAccessToAllHosts()) 538 return true; 539 540 const URLPatternSet& old_list = effective_hosts(); 541 const URLPatternSet& new_list = permissions->effective_hosts(); 542 543 // TODO(jstritar): This is overly conservative with respect to subdomains. 544 // For example, going from *.google.com to www.google.com will be 545 // considered an elevation, even though it is not (http://crbug.com/65337). 546 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false)); 547 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false)); 548 std::set<std::string> new_hosts_only; 549 550 std::set_difference(new_hosts_set.begin(), new_hosts_set.end(), 551 old_hosts_set.begin(), old_hosts_set.end(), 552 std::inserter(new_hosts_only, new_hosts_only.begin())); 553 554 return !new_hosts_only.empty(); 555 } 556 557 } // namespace extensions 558