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 for (PermissionMessages::const_iterator i = permissions.begin(); | |
192 i != permissions.end(); ++i) { | |
193 switch (i->id()) { | |
194 case PermissionMessage::kAudioCapture: | |
195 audio_capture = true; | |
196 break; | |
197 case PermissionMessage::kVideoCapture: | |
198 video_capture = true; | |
199 break; | |
200 default: | |
201 break; | |
202 } | |
203 } | |
204 | |
205 for (PermissionMessages::const_iterator i = permissions.begin(); | |
206 i != permissions.end(); ++i) { | |
207 int id = i->id(); | |
208 if (audio_capture && video_capture) { | |
209 if (id == PermissionMessage::kAudioCapture) { | |
210 messages.push_back(l10n_util::GetStringUTF16( | |
211 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE)); | |
212 continue; | |
213 } else if (id == PermissionMessage::kVideoCapture) { | |
214 // The combined message will be pushed above. | |
215 continue; | |
216 } | |
217 } | |
218 | |
219 messages.push_back(i->message()); | |
220 } | |
221 | |
222 return messages; | |
223 } | |
224 | |
225 std::vector<string16> PermissionSet::GetWarningMessagesDetails( | |
226 Manifest::Type extension_type) const { | |
227 std::vector<string16> messages; | |
228 PermissionMessages permissions = GetPermissionMessages(extension_type); | |
229 | |
230 for (PermissionMessages::const_iterator i = permissions.begin(); | |
231 i != permissions.end(); ++i) | |
232 messages.push_back(i->details()); | |
233 | |
234 return messages; | |
235 } | |
236 | |
237 bool PermissionSet::IsEmpty() const { | 144 bool PermissionSet::IsEmpty() const { |
238 // Not default if any host permissions are present. | 145 // Not default if any host permissions are present. |
239 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) | 146 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty())) |
240 return false; | 147 return false; |
241 | 148 |
242 // Or if it has no api permissions. | 149 // Or if it has no api permissions. |
243 return apis().empty(); | 150 return apis().empty(); |
244 } | 151 } |
245 | 152 |
246 bool PermissionSet::HasAPIPermission( | 153 bool PermissionSet::HasAPIPermission( |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 | 213 |
307 bool PermissionSet::HasEffectiveFullAccess() const { | 214 bool PermissionSet::HasEffectiveFullAccess() const { |
308 for (APIPermissionSet::const_iterator i = apis().begin(); | 215 for (APIPermissionSet::const_iterator i = apis().begin(); |
309 i != apis().end(); ++i) { | 216 i != apis().end(); ++i) { |
310 if (i->info()->implies_full_access()) | 217 if (i->info()->implies_full_access()) |
311 return true; | 218 return true; |
312 } | 219 } |
313 return false; | 220 return false; |
314 } | 221 } |
315 | 222 |
316 bool PermissionSet::HasLessPrivilegesThan( | |
317 const PermissionSet* permissions, | |
318 Manifest::Type extension_type) const { | |
319 // Things can't get worse than native code access. | |
320 if (HasEffectiveFullAccess()) | |
321 return false; | |
322 | |
323 // Otherwise, it's a privilege increase if the new one has full access. | |
324 if (permissions->HasEffectiveFullAccess()) | |
325 return true; | |
326 | |
327 if (HasLessHostPrivilegesThan(permissions, extension_type)) | |
328 return true; | |
329 | |
330 if (HasLessAPIPrivilegesThan(permissions)) | |
331 return true; | |
332 | |
333 return false; | |
334 } | |
335 | |
336 PermissionSet::~PermissionSet() {} | 223 PermissionSet::~PermissionSet() {} |
337 | 224 |
338 // static | |
339 std::set<std::string> PermissionSet::GetDistinctHosts( | |
340 const URLPatternSet& host_patterns, | |
341 bool include_rcd, | |
342 bool exclude_file_scheme) { | |
343 // Use a vector to preserve order (also faster than a map on small sets). | |
344 // Each item is a host split into two parts: host without RCDs and | |
345 // current best RCD. | |
346 typedef std::vector<std::pair<std::string, std::string> > HostVector; | |
347 HostVector hosts_best_rcd; | |
348 for (URLPatternSet::const_iterator i = host_patterns.begin(); | |
349 i != host_patterns.end(); ++i) { | |
350 if (exclude_file_scheme && i->scheme() == chrome::kFileScheme) | |
351 continue; | |
352 | |
353 std::string host = i->host(); | |
354 | |
355 // Add the subdomain wildcard back to the host, if necessary. | |
356 if (i->match_subdomains()) | |
357 host = "*." + host; | |
358 | |
359 // If the host has an RCD, split it off so we can detect duplicates. | |
360 std::string rcd; | |
361 size_t reg_len = net::registry_controlled_domains::GetRegistryLength( | |
362 host, | |
363 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, | |
364 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); | |
365 if (reg_len && reg_len != std::string::npos) { | |
366 if (include_rcd) // else leave rcd empty | |
367 rcd = host.substr(host.size() - reg_len); | |
368 host = host.substr(0, host.size() - reg_len); | |
369 } | |
370 | |
371 // Check if we've already seen this host. | |
372 HostVector::iterator it = hosts_best_rcd.begin(); | |
373 for (; it != hosts_best_rcd.end(); ++it) { | |
374 if (it->first == host) | |
375 break; | |
376 } | |
377 // If this host was found, replace the RCD if this one is better. | |
378 if (it != hosts_best_rcd.end()) { | |
379 if (include_rcd && RcdBetterThan(rcd, it->second)) | |
380 it->second = rcd; | |
381 } else { // Previously unseen host, append it. | |
382 hosts_best_rcd.push_back(std::make_pair(host, rcd)); | |
383 } | |
384 } | |
385 | |
386 // Build up the final vector by concatenating hosts and RCDs. | |
387 std::set<std::string> distinct_hosts; | |
388 for (HostVector::iterator it = hosts_best_rcd.begin(); | |
389 it != hosts_best_rcd.end(); ++it) | |
390 distinct_hosts.insert(it->first + it->second); | |
391 return distinct_hosts; | |
392 } | |
393 | |
394 void PermissionSet::InitImplicitPermissions() { | 225 void PermissionSet::InitImplicitPermissions() { |
395 // The downloads permission implies the internal version as well. | 226 // The downloads permission implies the internal version as well. |
396 if (apis_.find(APIPermission::kDownloads) != apis_.end()) | 227 if (apis_.find(APIPermission::kDownloads) != apis_.end()) |
397 apis_.insert(APIPermission::kDownloadsInternal); | 228 apis_.insert(APIPermission::kDownloadsInternal); |
398 | 229 |
399 // 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 |
400 // API without exposing it to the Chrome App? | 231 // API without exposing it to the Chrome App? |
401 if (apis_.find(APIPermission::kWebView) != apis_.end()) | 232 if (apis_.find(APIPermission::kWebView) != apis_.end()) |
402 apis_.insert(APIPermission::kWebRequestInternal); | 233 apis_.insert(APIPermission::kWebRequestInternal); |
403 | 234 |
404 // The webRequest permission implies the internal version as well. | 235 // The webRequest permission implies the internal version as well. |
405 if (apis_.find(APIPermission::kWebRequest) != apis_.end()) | 236 if (apis_.find(APIPermission::kWebRequest) != apis_.end()) |
406 apis_.insert(APIPermission::kWebRequestInternal); | 237 apis_.insert(APIPermission::kWebRequestInternal); |
407 | 238 |
408 // The fileBrowserHandler permission implies the internal version as well. | 239 // The fileBrowserHandler permission implies the internal version as well. |
409 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end()) | 240 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end()) |
410 apis_.insert(APIPermission::kFileBrowserHandlerInternal); | 241 apis_.insert(APIPermission::kFileBrowserHandlerInternal); |
411 } | 242 } |
412 | 243 |
413 void PermissionSet::InitEffectiveHosts() { | 244 void PermissionSet::InitEffectiveHosts() { |
414 effective_hosts_.ClearPatterns(); | 245 effective_hosts_.ClearPatterns(); |
415 | 246 |
416 URLPatternSet::CreateUnion( | 247 URLPatternSet::CreateUnion( |
417 explicit_hosts(), scriptable_hosts(), &effective_hosts_); | 248 explicit_hosts(), scriptable_hosts(), &effective_hosts_); |
418 } | 249 } |
419 | 250 |
420 std::set<PermissionMessage> PermissionSet::GetAPIPermissionMessages() const { | |
421 std::set<PermissionMessage> messages; | |
422 for (APIPermissionSet::const_iterator permission_it = apis_.begin(); | |
423 permission_it != apis_.end(); ++permission_it) { | |
424 if (permission_it->HasMessages()) { | |
425 PermissionMessages new_messages = permission_it->GetMessages(); | |
426 messages.insert(new_messages.begin(), new_messages.end()); | |
427 } | |
428 } | |
429 | |
430 // A special hack: If kFileSystemWriteDirectory would be displayed, hide | |
431 // kFileSystemDirectory and and kFileSystemWrite as the write directory | |
432 // message implies the other two. | |
433 // TODO(sammc): Remove this. See http://crbug.com/284849. | |
434 std::set<PermissionMessage>::iterator write_directory_message = | |
435 messages.find(PermissionMessage( | |
436 PermissionMessage::kFileSystemWriteDirectory, string16())); | |
437 if (write_directory_message != messages.end()) { | |
438 messages.erase( | |
439 PermissionMessage(PermissionMessage::kFileSystemWrite, string16())); | |
440 messages.erase( | |
441 PermissionMessage(PermissionMessage::kFileSystemDirectory, string16())); | |
442 } | |
443 | |
444 // A special hack: The warning message for declarativeWebRequest | |
445 // permissions speaks about blocking parts of pages, which is a | |
446 // subset of what the "<all_urls>" access allows. Therefore we | |
447 // display only the "<all_urls>" warning message if both permissions | |
448 // are required. | |
449 if (HasEffectiveAccessToAllHosts()) { | |
450 messages.erase( | |
451 PermissionMessage( | |
452 PermissionMessage::kDeclarativeWebRequest, string16())); | |
453 } | |
454 | |
455 return messages; | |
456 } | |
457 | |
458 std::set<PermissionMessage> PermissionSet::GetHostPermissionMessages( | |
459 Manifest::Type extension_type) const { | |
460 // Since platform apps always use isolated storage, they can't (silently) | |
461 // access user data on other domains, so there's no need to prompt. | |
462 // Note: this must remain consistent with HasLessHostPrivilegesThan. | |
463 // See crbug.com/255229. | |
464 std::set<PermissionMessage> messages; | |
465 if (extension_type == Manifest::TYPE_PLATFORM_APP) | |
466 return messages; | |
467 | |
468 if (HasEffectiveAccessToAllHosts()) { | |
469 messages.insert(PermissionMessage( | |
470 PermissionMessage::kHostsAll, | |
471 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); | |
472 } else { | |
473 URLPatternSet regular_hosts; | |
474 ExtensionsClient::Get()->FilterHostPermissions( | |
475 effective_hosts_, ®ular_hosts, &messages); | |
476 | |
477 std::set<std::string> hosts = GetDistinctHosts(regular_hosts, true, true); | |
478 if (!hosts.empty()) | |
479 messages.insert(permission_message_util::CreateFromHostList(hosts)); | |
480 } | |
481 return messages; | |
482 } | |
483 | |
484 bool PermissionSet::HasLessAPIPrivilegesThan( | |
485 const PermissionSet* permissions) const { | |
486 if (permissions == NULL) | |
487 return false; | |
488 | |
489 typedef std::set<PermissionMessage> PermissionMsgSet; | |
490 PermissionMsgSet current_warnings = GetAPIPermissionMessages(); | |
491 PermissionMsgSet new_warnings = permissions->GetAPIPermissionMessages(); | |
492 PermissionMsgSet delta_warnings = | |
493 base::STLSetDifference<PermissionMsgSet>(new_warnings, current_warnings); | |
494 | |
495 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and | |
496 // kFileSystemWrite. | |
497 // TODO(sammc): Remove this. See http://crbug.com/284849. | |
498 if (current_warnings.find(PermissionMessage( | |
499 PermissionMessage::kFileSystemWriteDirectory, string16())) != | |
500 current_warnings.end()) { | |
501 delta_warnings.erase( | |
502 PermissionMessage(PermissionMessage::kFileSystemDirectory, string16())); | |
503 delta_warnings.erase( | |
504 PermissionMessage(PermissionMessage::kFileSystemWrite, string16())); | |
505 } | |
506 | |
507 // We have less privileges if there are additional warnings present. | |
508 return !delta_warnings.empty(); | |
509 } | |
510 | |
511 bool PermissionSet::HasLessHostPrivilegesThan( | |
512 const PermissionSet* permissions, | |
513 Manifest::Type extension_type) const { | |
514 // Platform apps host permission changes do not count as privilege increases. | |
515 // Note: this must remain consistent with GetHostPermissionMessages. | |
516 if (extension_type == Manifest::TYPE_PLATFORM_APP) | |
517 return false; | |
518 | |
519 // If this permission set can access any host, then it can't be elevated. | |
520 if (HasEffectiveAccessToAllHosts()) | |
521 return false; | |
522 | |
523 // Likewise, if the other permission set has full host access, then it must be | |
524 // a privilege increase. | |
525 if (permissions->HasEffectiveAccessToAllHosts()) | |
526 return true; | |
527 | |
528 const URLPatternSet& old_list = effective_hosts(); | |
529 const URLPatternSet& new_list = permissions->effective_hosts(); | |
530 | |
531 // TODO(jstritar): This is overly conservative with respect to subdomains. | |
532 // For example, going from *.google.com to www.google.com will be | |
533 // considered an elevation, even though it is not (http://crbug.com/65337). | |
534 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false)); | |
535 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false)); | |
536 std::set<std::string> new_hosts_only = | |
537 base::STLSetDifference<std::set<std::string> >(new_hosts_set, | |
538 old_hosts_set); | |
539 | |
540 return !new_hosts_only.empty(); | |
541 } | |
542 | |
543 } // namespace extensions | 251 } // namespace extensions |
OLD | NEW |