| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/common/extensions/permissions/permission_set.h" | 5 #include "chrome/common/extensions/permissions/permission_set.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <iterator> | 8 #include <iterator> |
| 9 #include <string> | 9 #include <string> |
| 10 | 10 |
| 11 #include "base/stl_util.h" | |
| 12 #include "chrome/common/extensions/permissions/permission_message_util.h" | |
| 13 #include "content/public/common/url_constants.h" | |
| 14 #include "extensions/common/extensions_client.h" | |
| 15 #include "extensions/common/permissions/permissions_info.h" | 11 #include "extensions/common/permissions/permissions_info.h" |
| 16 #include "extensions/common/url_pattern.h" | 12 #include "extensions/common/url_pattern.h" |
| 17 #include "extensions/common/url_pattern_set.h" | 13 #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" | 14 #include "url/gurl.h" |
| 22 | 15 |
| 23 using extensions::URLPatternSet; | 16 using extensions::URLPatternSet; |
| 24 | 17 |
| 25 namespace { | 18 namespace { |
| 26 | 19 |
| 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) { | 20 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) { |
| 41 DCHECK(out); | 21 DCHECK(out); |
| 42 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) { | 22 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) { |
| 43 URLPattern p = *i; | 23 URLPattern p = *i; |
| 44 p.SetPath("/*"); | 24 p.SetPath("/*"); |
| 45 out->AddPattern(p); | 25 out->AddPattern(p); |
| 46 } | 26 } |
| 47 } | 27 } |
| 48 | 28 |
| 49 } // namespace | 29 } // namespace |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 154 | 134 |
| 155 std::set<std::string> PermissionSet::GetAPIsAsStrings() const { | 135 std::set<std::string> PermissionSet::GetAPIsAsStrings() const { |
| 156 std::set<std::string> apis_str; | 136 std::set<std::string> apis_str; |
| 157 for (APIPermissionSet::const_iterator i = apis_.begin(); | 137 for (APIPermissionSet::const_iterator i = apis_.begin(); |
| 158 i != apis_.end(); ++i) { | 138 i != apis_.end(); ++i) { |
| 159 apis_str.insert(i->name()); | 139 apis_str.insert(i->name()); |
| 160 } | 140 } |
| 161 return apis_str; | 141 return apis_str; |
| 162 } | 142 } |
| 163 | 143 |
| 164 PermissionMessages PermissionSet::GetPermissionMessages( | |
| 165 Manifest::Type extension_type) const { | |
| 166 PermissionMessages messages; | |
| 167 | |
| 168 if (HasEffectiveFullAccess()) { | |
| 169 messages.push_back(PermissionMessage( | |
| 170 PermissionMessage::kFullAccess, | |
| 171 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS))); | |
| 172 return messages; | |
| 173 } | |
| 174 | |
| 175 std::set<PermissionMessage> host_msgs = | |
| 176 GetHostPermissionMessages(extension_type); | |
| 177 std::set<PermissionMessage> api_msgs = GetAPIPermissionMessages(); | |
| 178 messages.insert(messages.end(), host_msgs.begin(), host_msgs.end()); | |
| 179 messages.insert(messages.end(), api_msgs.begin(), api_msgs.end()); | |
| 180 | |
| 181 return messages; | |
| 182 } | |
| 183 | |
| 184 std::vector<string16> PermissionSet::GetWarningMessages( | |
| 185 Manifest::Type extension_type) const { | |
| 186 std::vector<string16> messages; | |
| 187 PermissionMessages permissions = GetPermissionMessages(extension_type); | |
| 188 | |
| 189 bool audio_capture = false; | |
| 190 bool video_capture = false; | |
| 191 bool media_galleries_read = false; | |
| 192 bool media_galleries_copy_to = false; | |
| 193 for (PermissionMessages::const_iterator i = permissions.begin(); | |
| 194 i != permissions.end(); ++i) { | |
| 195 switch (i->id()) { | |
| 196 case PermissionMessage::kAudioCapture: | |
| 197 audio_capture = true; | |
| 198 break; | |
| 199 case PermissionMessage::kVideoCapture: | |
| 200 video_capture = true; | |
| 201 break; | |
| 202 case PermissionMessage::kMediaGalleriesAllGalleriesRead: | |
| 203 media_galleries_read = true; | |
| 204 break; | |
| 205 case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo: | |
| 206 media_galleries_copy_to = true; | |
| 207 break; | |
| 208 default: | |
| 209 break; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 for (PermissionMessages::const_iterator i = permissions.begin(); | |
| 214 i != permissions.end(); ++i) { | |
| 215 int id = i->id(); | |
| 216 if (audio_capture && video_capture) { | |
| 217 if (id == PermissionMessage::kAudioCapture) { | |
| 218 messages.push_back(l10n_util::GetStringUTF16( | |
| 219 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE)); | |
| 220 continue; | |
| 221 } else if (id == PermissionMessage::kVideoCapture) { | |
| 222 // The combined message will be pushed above. | |
| 223 continue; | |
| 224 } | |
| 225 } | |
| 226 if (media_galleries_read && media_galleries_copy_to) { | |
| 227 if (id == PermissionMessage::kMediaGalleriesAllGalleriesRead) { | |
| 228 messages.push_back(l10n_util::GetStringUTF16( | |
| 229 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE)); | |
| 230 continue; | |
| 231 } else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) { | |
| 232 // The combined message will be pushed above. | |
| 233 continue; | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 messages.push_back(i->message()); | |
| 238 } | |
| 239 | |
| 240 return messages; | |
| 241 } | |
| 242 | |
| 243 std::vector<string16> PermissionSet::GetWarningMessagesDetails( | |
| 244 Manifest::Type extension_type) const { | |
| 245 std::vector<string16> messages; | |
| 246 PermissionMessages permissions = GetPermissionMessages(extension_type); | |
| 247 | |
| 248 for (PermissionMessages::const_iterator i = permissions.begin(); | |
| 249 i != permissions.end(); ++i) | |
| 250 messages.push_back(i->details()); | |
| 251 | |
| 252 return messages; | |
| 253 } | |
| 254 | |
| 255 bool PermissionSet::IsEmpty() const { | 144 bool PermissionSet::IsEmpty() const { |
| 256 // Not default if any host permissions are present. | 145 // Not default if any host permissions are present. |
| 257 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) | 146 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) |
| 258 return false; | 147 return false; |
| 259 | 148 |
| 260 // Or if it has no api permissions. | 149 // Or if it has no api permissions. |
| 261 return apis().empty(); | 150 return apis().empty(); |
| 262 } | 151 } |
| 263 | 152 |
| 264 bool PermissionSet::HasAPIPermission( | 153 bool PermissionSet::HasAPIPermission( |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 324 | 213 |
| 325 bool PermissionSet::HasEffectiveFullAccess() const { | 214 bool PermissionSet::HasEffectiveFullAccess() const { |
| 326 for (APIPermissionSet::const_iterator i = apis().begin(); | 215 for (APIPermissionSet::const_iterator i = apis().begin(); |
| 327 i != apis().end(); ++i) { | 216 i != apis().end(); ++i) { |
| 328 if (i->info()->implies_full_access()) | 217 if (i->info()->implies_full_access()) |
| 329 return true; | 218 return true; |
| 330 } | 219 } |
| 331 return false; | 220 return false; |
| 332 } | 221 } |
| 333 | 222 |
| 334 bool PermissionSet::HasLessPrivilegesThan( | |
| 335 const PermissionSet* permissions, | |
| 336 Manifest::Type extension_type) const { | |
| 337 // Things can't get worse than native code access. | |
| 338 if (HasEffectiveFullAccess()) | |
| 339 return false; | |
| 340 | |
| 341 // Otherwise, it's a privilege increase if the new one has full access. | |
| 342 if (permissions->HasEffectiveFullAccess()) | |
| 343 return true; | |
| 344 | |
| 345 if (HasLessHostPrivilegesThan(permissions, extension_type)) | |
| 346 return true; | |
| 347 | |
| 348 if (HasLessAPIPrivilegesThan(permissions)) | |
| 349 return true; | |
| 350 | |
| 351 return false; | |
| 352 } | |
| 353 | |
| 354 PermissionSet::~PermissionSet() {} | 223 PermissionSet::~PermissionSet() {} |
| 355 | 224 |
| 356 // static | |
| 357 std::set<std::string> PermissionSet::GetDistinctHosts( | |
| 358 const URLPatternSet& host_patterns, | |
| 359 bool include_rcd, | |
| 360 bool exclude_file_scheme) { | |
| 361 // Use a vector to preserve order (also faster than a map on small sets). | |
| 362 // Each item is a host split into two parts: host without RCDs and | |
| 363 // current best RCD. | |
| 364 typedef std::vector<std::pair<std::string, std::string> > HostVector; | |
| 365 HostVector hosts_best_rcd; | |
| 366 for (URLPatternSet::const_iterator i = host_patterns.begin(); | |
| 367 i != host_patterns.end(); ++i) { | |
| 368 if (exclude_file_scheme && i->scheme() == chrome::kFileScheme) | |
| 369 continue; | |
| 370 | |
| 371 std::string host = i->host(); | |
| 372 | |
| 373 // Add the subdomain wildcard back to the host, if necessary. | |
| 374 if (i->match_subdomains()) | |
| 375 host = "*." + host; | |
| 376 | |
| 377 // If the host has an RCD, split it off so we can detect duplicates. | |
| 378 std::string rcd; | |
| 379 size_t reg_len = net::registry_controlled_domains::GetRegistryLength( | |
| 380 host, | |
| 381 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, | |
| 382 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); | |
| 383 if (reg_len && reg_len != std::string::npos) { | |
| 384 if (include_rcd) // else leave rcd empty | |
| 385 rcd = host.substr(host.size() - reg_len); | |
| 386 host = host.substr(0, host.size() - reg_len); | |
| 387 } | |
| 388 | |
| 389 // Check if we've already seen this host. | |
| 390 HostVector::iterator it = hosts_best_rcd.begin(); | |
| 391 for (; it != hosts_best_rcd.end(); ++it) { | |
| 392 if (it->first == host) | |
| 393 break; | |
| 394 } | |
| 395 // If this host was found, replace the RCD if this one is better. | |
| 396 if (it != hosts_best_rcd.end()) { | |
| 397 if (include_rcd && RcdBetterThan(rcd, it->second)) | |
| 398 it->second = rcd; | |
| 399 } else { // Previously unseen host, append it. | |
| 400 hosts_best_rcd.push_back(std::make_pair(host, rcd)); | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 // Build up the final vector by concatenating hosts and RCDs. | |
| 405 std::set<std::string> distinct_hosts; | |
| 406 for (HostVector::iterator it = hosts_best_rcd.begin(); | |
| 407 it != hosts_best_rcd.end(); ++it) | |
| 408 distinct_hosts.insert(it->first + it->second); | |
| 409 return distinct_hosts; | |
| 410 } | |
| 411 | |
| 412 void PermissionSet::InitImplicitPermissions() { | 225 void PermissionSet::InitImplicitPermissions() { |
| 413 // The downloads permission implies the internal version as well. | 226 // The downloads permission implies the internal version as well. |
| 414 if (apis_.find(APIPermission::kDownloads) != apis_.end()) | 227 if (apis_.find(APIPermission::kDownloads) != apis_.end()) |
| 415 apis_.insert(APIPermission::kDownloadsInternal); | 228 apis_.insert(APIPermission::kDownloadsInternal); |
| 416 | 229 |
| 417 // TODO(fsamuel): Is there a better way to request access to the WebRequest | 230 // TODO(fsamuel): Is there a better way to request access to the WebRequest |
| 418 // API without exposing it to the Chrome App? | 231 // API without exposing it to the Chrome App? |
| 419 if (apis_.find(APIPermission::kWebView) != apis_.end()) | 232 if (apis_.find(APIPermission::kWebView) != apis_.end()) |
| 420 apis_.insert(APIPermission::kWebRequestInternal); | 233 apis_.insert(APIPermission::kWebRequestInternal); |
| 421 | 234 |
| 422 // The webRequest permission implies the internal version as well. | 235 // The webRequest permission implies the internal version as well. |
| 423 if (apis_.find(APIPermission::kWebRequest) != apis_.end()) | 236 if (apis_.find(APIPermission::kWebRequest) != apis_.end()) |
| 424 apis_.insert(APIPermission::kWebRequestInternal); | 237 apis_.insert(APIPermission::kWebRequestInternal); |
| 425 | 238 |
| 426 // The fileBrowserHandler permission implies the internal version as well. | 239 // The fileBrowserHandler permission implies the internal version as well. |
| 427 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end()) | 240 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end()) |
| 428 apis_.insert(APIPermission::kFileBrowserHandlerInternal); | 241 apis_.insert(APIPermission::kFileBrowserHandlerInternal); |
| 429 } | 242 } |
| 430 | 243 |
| 431 void PermissionSet::InitEffectiveHosts() { | 244 void PermissionSet::InitEffectiveHosts() { |
| 432 effective_hosts_.ClearPatterns(); | 245 effective_hosts_.ClearPatterns(); |
| 433 | 246 |
| 434 URLPatternSet::CreateUnion( | 247 URLPatternSet::CreateUnion( |
| 435 explicit_hosts(), scriptable_hosts(), &effective_hosts_); | 248 explicit_hosts(), scriptable_hosts(), &effective_hosts_); |
| 436 } | 249 } |
| 437 | 250 |
| 438 std::set<PermissionMessage> PermissionSet::GetAPIPermissionMessages() const { | |
| 439 std::set<PermissionMessage> messages; | |
| 440 for (APIPermissionSet::const_iterator permission_it = apis_.begin(); | |
| 441 permission_it != apis_.end(); ++permission_it) { | |
| 442 if (permission_it->HasMessages()) { | |
| 443 PermissionMessages new_messages = permission_it->GetMessages(); | |
| 444 messages.insert(new_messages.begin(), new_messages.end()); | |
| 445 } | |
| 446 } | |
| 447 | |
| 448 // A special hack: If kFileSystemWriteDirectory would be displayed, hide | |
| 449 // kFileSystemDirectory and and kFileSystemWrite as the write directory | |
| 450 // message implies the other two. | |
| 451 // TODO(sammc): Remove this. See http://crbug.com/284849. | |
| 452 std::set<PermissionMessage>::iterator write_directory_message = | |
| 453 messages.find(PermissionMessage( | |
| 454 PermissionMessage::kFileSystemWriteDirectory, string16())); | |
| 455 if (write_directory_message != messages.end()) { | |
| 456 messages.erase( | |
| 457 PermissionMessage(PermissionMessage::kFileSystemWrite, string16())); | |
| 458 messages.erase( | |
| 459 PermissionMessage(PermissionMessage::kFileSystemDirectory, string16())); | |
| 460 } | |
| 461 | |
| 462 // A special hack: The warning message for declarativeWebRequest | |
| 463 // permissions speaks about blocking parts of pages, which is a | |
| 464 // subset of what the "<all_urls>" access allows. Therefore we | |
| 465 // display only the "<all_urls>" warning message if both permissions | |
| 466 // are required. | |
| 467 if (HasEffectiveAccessToAllHosts()) { | |
| 468 messages.erase( | |
| 469 PermissionMessage( | |
| 470 PermissionMessage::kDeclarativeWebRequest, string16())); | |
| 471 } | |
| 472 | |
| 473 return messages; | |
| 474 } | |
| 475 | |
| 476 std::set<PermissionMessage> PermissionSet::GetHostPermissionMessages( | |
| 477 Manifest::Type extension_type) const { | |
| 478 // Since platform apps always use isolated storage, they can't (silently) | |
| 479 // access user data on other domains, so there's no need to prompt. | |
| 480 // Note: this must remain consistent with HasLessHostPrivilegesThan. | |
| 481 // See crbug.com/255229. | |
| 482 std::set<PermissionMessage> messages; | |
| 483 if (extension_type == Manifest::TYPE_PLATFORM_APP) | |
| 484 return messages; | |
| 485 | |
| 486 if (HasEffectiveAccessToAllHosts()) { | |
| 487 messages.insert(PermissionMessage( | |
| 488 PermissionMessage::kHostsAll, | |
| 489 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); | |
| 490 } else { | |
| 491 URLPatternSet regular_hosts; | |
| 492 ExtensionsClient::Get()->FilterHostPermissions( | |
| 493 effective_hosts_, ®ular_hosts, &messages); | |
| 494 | |
| 495 std::set<std::string> hosts = GetDistinctHosts(regular_hosts, true, true); | |
| 496 if (!hosts.empty()) | |
| 497 messages.insert(permission_message_util::CreateFromHostList(hosts)); | |
| 498 } | |
| 499 return messages; | |
| 500 } | |
| 501 | |
| 502 bool PermissionSet::HasLessAPIPrivilegesThan( | |
| 503 const PermissionSet* permissions) const { | |
| 504 if (permissions == NULL) | |
| 505 return false; | |
| 506 | |
| 507 typedef std::set<PermissionMessage> PermissionMsgSet; | |
| 508 PermissionMsgSet current_warnings = GetAPIPermissionMessages(); | |
| 509 PermissionMsgSet new_warnings = permissions->GetAPIPermissionMessages(); | |
| 510 PermissionMsgSet delta_warnings = | |
| 511 base::STLSetDifference<PermissionMsgSet>(new_warnings, current_warnings); | |
| 512 | |
| 513 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and | |
| 514 // kFileSystemWrite. | |
| 515 // TODO(sammc): Remove this. See http://crbug.com/284849. | |
| 516 if (current_warnings.find(PermissionMessage( | |
| 517 PermissionMessage::kFileSystemWriteDirectory, string16())) != | |
| 518 current_warnings.end()) { | |
| 519 delta_warnings.erase( | |
| 520 PermissionMessage(PermissionMessage::kFileSystemDirectory, string16())); | |
| 521 delta_warnings.erase( | |
| 522 PermissionMessage(PermissionMessage::kFileSystemWrite, string16())); | |
| 523 } | |
| 524 | |
| 525 // We have less privileges if there are additional warnings present. | |
| 526 return !delta_warnings.empty(); | |
| 527 } | |
| 528 | |
| 529 bool PermissionSet::HasLessHostPrivilegesThan( | |
| 530 const PermissionSet* permissions, | |
| 531 Manifest::Type extension_type) const { | |
| 532 // Platform apps host permission changes do not count as privilege increases. | |
| 533 // Note: this must remain consistent with GetHostPermissionMessages. | |
| 534 if (extension_type == Manifest::TYPE_PLATFORM_APP) | |
| 535 return false; | |
| 536 | |
| 537 // If this permission set can access any host, then it can't be elevated. | |
| 538 if (HasEffectiveAccessToAllHosts()) | |
| 539 return false; | |
| 540 | |
| 541 // Likewise, if the other permission set has full host access, then it must be | |
| 542 // a privilege increase. | |
| 543 if (permissions->HasEffectiveAccessToAllHosts()) | |
| 544 return true; | |
| 545 | |
| 546 const URLPatternSet& old_list = effective_hosts(); | |
| 547 const URLPatternSet& new_list = permissions->effective_hosts(); | |
| 548 | |
| 549 // TODO(jstritar): This is overly conservative with respect to subdomains. | |
| 550 // For example, going from *.google.com to www.google.com will be | |
| 551 // considered an elevation, even though it is not (http://crbug.com/65337). | |
| 552 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false)); | |
| 553 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false)); | |
| 554 std::set<std::string> new_hosts_only = | |
| 555 base::STLSetDifference<std::set<std::string> >(new_hosts_set, | |
| 556 old_hosts_set); | |
| 557 | |
| 558 return !new_hosts_only.empty(); | |
| 559 } | |
| 560 | |
| 561 } // namespace extensions | 251 } // namespace extensions |
| OLD | NEW |