chrome_permission_message_provider.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
1// Copyright 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/chrome_permission_message_provider.h" 6 7#include "base/stl_util.h" 8#include "base/strings/stringprintf.h" 9#include "extensions/common/extensions_client.h" 10#include "extensions/common/permissions/permission_message.h" 11#include "extensions/common/permissions/permission_message_util.h" 12#include "extensions/common/permissions/permission_set.h" 13#include "extensions/common/url_pattern.h" 14#include "extensions/common/url_pattern_set.h" 15#include "grit/generated_resources.h" 16#include "net/base/registry_controlled_domains/registry_controlled_domain.h" 17#include "ui/base/l10n/l10n_util.h" 18#include "url/gurl.h" 19 20namespace extensions { 21 22namespace { 23 24bool ShouldWarnAllHosts(const PermissionSet* permissions) { 25 if (permissions->HasEffectiveAccessToAllHosts()) 26 return true; 27 28 const URLPatternSet& effective_hosts = permissions->effective_hosts(); 29 for (URLPatternSet::const_iterator iter = effective_hosts.begin(); 30 iter != effective_hosts.end(); 31 ++iter) { 32 // If this doesn't even match subdomains, it can't possibly imply all hosts. 33 if (!iter->match_subdomains()) 34 continue; 35 36 // If iter->host() is a recognized TLD, this will be 0. We don't include 37 // private TLDs, so that, e.g., *.appspot.com does not imply all hosts. 38 size_t registry_length = 39 net::registry_controlled_domains::GetRegistryLength( 40 iter->host(), 41 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, 42 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 43 // If there was more than just a TLD in the host (e.g., *.foobar.com), it 44 // doesn't imply all hosts. 45 if (registry_length > 0) 46 continue; 47 48 // At this point the host could either be just a TLD ("com") or some unknown 49 // TLD-like string ("notatld"). To disambiguate between them construct a 50 // fake URL, and check the registry. This returns 0 if the TLD is 51 // unrecognized, or the length of the recognized TLD. 52 registry_length = net::registry_controlled_domains::GetRegistryLength( 53 base::StringPrintf("foo.%s", iter->host().c_str()), 54 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, 55 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 56 // If we recognized this TLD, then this is a pattern like *.com, and it 57 // should imply all hosts. 58 if (registry_length > 0) 59 return true; 60 } 61 62 return false; 63} 64 65PermissionMessages::iterator FindMessageByID(PermissionMessages& messages, 66 int id) { 67 for (PermissionMessages::iterator it = messages.begin(); 68 it != messages.end(); ++it) { 69 if (it->id() == id) 70 return it; 71 } 72 73 return messages.end(); 74} 75 76} // namespace 77 78ChromePermissionMessageProvider::ChromePermissionMessageProvider() { 79} 80 81ChromePermissionMessageProvider::~ChromePermissionMessageProvider() { 82} 83 84// static 85PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages( 86 const PermissionSet* permissions, 87 Manifest::Type extension_type) const { 88 PermissionMessages messages; 89 90 if (permissions->HasEffectiveFullAccess()) { 91 messages.push_back(PermissionMessage( 92 PermissionMessage::kFullAccess, 93 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS))); 94 return messages; 95 } 96 97 std::set<PermissionMessage> host_msgs = 98 GetHostPermissionMessages(permissions, extension_type); 99 std::set<PermissionMessage> api_msgs = GetAPIPermissionMessages(permissions); 100 std::set<PermissionMessage> manifest_permission_msgs = 101 GetManifestPermissionMessages(permissions); 102 messages.insert(messages.end(), host_msgs.begin(), host_msgs.end()); 103 messages.insert(messages.end(), api_msgs.begin(), api_msgs.end()); 104 messages.insert(messages.end(), manifest_permission_msgs.begin(), 105 manifest_permission_msgs.end()); 106 107 // Special hack: bookmarks permission message supersedes override bookmarks UI 108 // permission message if both permissions are specified. 109 PermissionMessages::iterator override_bookmarks_ui = 110 FindMessageByID(messages, PermissionMessage::kOverrideBookmarksUI); 111 if (override_bookmarks_ui != messages.end() && 112 FindMessageByID(messages, PermissionMessage::kBookmarks) != 113 messages.end()) { 114 messages.erase(override_bookmarks_ui); 115 } 116 117 return messages; 118} 119 120// static 121std::vector<base::string16> ChromePermissionMessageProvider::GetWarningMessages( 122 const PermissionSet* permissions, 123 Manifest::Type extension_type) const { 124 std::vector<base::string16> message_strings; 125 PermissionMessages messages = 126 GetPermissionMessages(permissions, extension_type); 127 128 bool audio_capture = false; 129 bool video_capture = false; 130 bool media_galleries_read = false; 131 bool media_galleries_copy_to = false; 132 bool media_galleries_delete = false; 133 for (PermissionMessages::const_iterator i = messages.begin(); 134 i != messages.end(); ++i) { 135 switch (i->id()) { 136 case PermissionMessage::kAudioCapture: 137 audio_capture = true; 138 break; 139 case PermissionMessage::kVideoCapture: 140 video_capture = true; 141 break; 142 case PermissionMessage::kMediaGalleriesAllGalleriesRead: 143 media_galleries_read = true; 144 break; 145 case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo: 146 media_galleries_copy_to = true; 147 break; 148 case PermissionMessage::kMediaGalleriesAllGalleriesDelete: 149 media_galleries_delete = true; 150 break; 151 default: 152 break; 153 } 154 } 155 156 for (PermissionMessages::const_iterator i = messages.begin(); 157 i != messages.end(); ++i) { 158 int id = i->id(); 159 if (audio_capture && video_capture) { 160 if (id == PermissionMessage::kAudioCapture) { 161 message_strings.push_back(l10n_util::GetStringUTF16( 162 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE)); 163 continue; 164 } else if (id == PermissionMessage::kVideoCapture) { 165 // The combined message will be pushed above. 166 continue; 167 } 168 } 169 if (media_galleries_read && 170 (media_galleries_copy_to || media_galleries_delete)) { 171 if (id == PermissionMessage::kMediaGalleriesAllGalleriesRead) { 172 int m_id = media_galleries_copy_to ? 173 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE : 174 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_DELETE; 175 message_strings.push_back(l10n_util::GetStringUTF16(m_id)); 176 continue; 177 } else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo || 178 id == PermissionMessage::kMediaGalleriesAllGalleriesDelete) { 179 // The combined message will be pushed above. 180 continue; 181 } 182 } 183 184 message_strings.push_back(i->message()); 185 } 186 187 return message_strings; 188} 189 190// static 191std::vector<base::string16> 192ChromePermissionMessageProvider::GetWarningMessagesDetails( 193 const PermissionSet* permissions, 194 Manifest::Type extension_type) const { 195 std::vector<base::string16> message_strings; 196 PermissionMessages messages = 197 GetPermissionMessages(permissions, extension_type); 198 199 for (PermissionMessages::const_iterator i = messages.begin(); 200 i != messages.end(); ++i) 201 message_strings.push_back(i->details()); 202 203 return message_strings; 204} 205 206// static 207bool ChromePermissionMessageProvider::IsPrivilegeIncrease( 208 const PermissionSet* old_permissions, 209 const PermissionSet* new_permissions, 210 Manifest::Type extension_type) const { 211 // Things can't get worse than native code access. 212 if (old_permissions->HasEffectiveFullAccess()) 213 return false; 214 215 // Otherwise, it's a privilege increase if the new one has full access. 216 if (new_permissions->HasEffectiveFullAccess()) 217 return true; 218 219 if (IsHostPrivilegeIncrease(old_permissions, new_permissions, extension_type)) 220 return true; 221 222 if (IsAPIPrivilegeIncrease(old_permissions, new_permissions)) 223 return true; 224 225 if (IsManifestPermissionPrivilegeIncrease(old_permissions, new_permissions)) 226 return true; 227 228 return false; 229} 230 231std::set<PermissionMessage> 232ChromePermissionMessageProvider::GetAPIPermissionMessages( 233 const PermissionSet* permissions) const { 234 std::set<PermissionMessage> messages; 235 for (APIPermissionSet::const_iterator permission_it = 236 permissions->apis().begin(); 237 permission_it != permissions->apis().end(); ++permission_it) { 238 if (permission_it->HasMessages()) { 239 PermissionMessages new_messages = permission_it->GetMessages(); 240 messages.insert(new_messages.begin(), new_messages.end()); 241 } 242 } 243 244 // A special hack: If kFileSystemWriteDirectory would be displayed, hide 245 // kFileSystemDirectory and and kFileSystemWrite as the write directory 246 // message implies the other two. 247 // TODO(sammc): Remove this. See http://crbug.com/284849. 248 std::set<PermissionMessage>::iterator write_directory_message = 249 messages.find(PermissionMessage( 250 PermissionMessage::kFileSystemWriteDirectory, base::string16())); 251 if (write_directory_message != messages.end()) { 252 messages.erase( 253 PermissionMessage(PermissionMessage::kFileSystemWrite, 254 base::string16())); 255 messages.erase( 256 PermissionMessage(PermissionMessage::kFileSystemDirectory, 257 base::string16())); 258 } 259 260 // A special hack: The warning message for declarativeWebRequest 261 // permissions speaks about blocking parts of pages, which is a 262 // subset of what the "<all_urls>" access allows. Therefore we 263 // display only the "<all_urls>" warning message if both permissions 264 // are required. 265 if (ShouldWarnAllHosts(permissions)) { 266 messages.erase( 267 PermissionMessage( 268 PermissionMessage::kDeclarativeWebRequest, base::string16())); 269 } 270 271 return messages; 272} 273 274std::set<PermissionMessage> 275ChromePermissionMessageProvider::GetManifestPermissionMessages( 276 const PermissionSet* permissions) const { 277 std::set<PermissionMessage> messages; 278 for (ManifestPermissionSet::const_iterator permission_it = 279 permissions->manifest_permissions().begin(); 280 permission_it != permissions->manifest_permissions().end(); 281 ++permission_it) { 282 if (permission_it->HasMessages()) { 283 PermissionMessages new_messages = permission_it->GetMessages(); 284 messages.insert(new_messages.begin(), new_messages.end()); 285 } 286 } 287 return messages; 288} 289 290std::set<PermissionMessage> 291ChromePermissionMessageProvider::GetHostPermissionMessages( 292 const PermissionSet* permissions, 293 Manifest::Type extension_type) const { 294 std::set<PermissionMessage> messages; 295 // Since platform apps always use isolated storage, they can't (silently) 296 // access user data on other domains, so there's no need to prompt. 297 // Note: this must remain consistent with IsHostPrivilegeIncrease. 298 // See crbug.com/255229. 299 if (extension_type == Manifest::TYPE_PLATFORM_APP) 300 return messages; 301 302 if (ShouldWarnAllHosts(permissions)) { 303 messages.insert(PermissionMessage( 304 PermissionMessage::kHostsAll, 305 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); 306 } else { 307 URLPatternSet regular_hosts; 308 ExtensionsClient::Get()->FilterHostPermissions( 309 permissions->effective_hosts(), ®ular_hosts, &messages); 310 311 std::set<std::string> hosts = 312 permission_message_util::GetDistinctHosts(regular_hosts, true, true); 313 if (!hosts.empty()) 314 messages.insert(permission_message_util::CreateFromHostList(hosts)); 315 } 316 return messages; 317} 318 319bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease( 320 const PermissionSet* old_permissions, 321 const PermissionSet* new_permissions) const { 322 if (new_permissions == NULL) 323 return false; 324 325 typedef std::set<PermissionMessage> PermissionMsgSet; 326 PermissionMsgSet old_warnings = GetAPIPermissionMessages(old_permissions); 327 PermissionMsgSet new_warnings = GetAPIPermissionMessages(new_permissions); 328 PermissionMsgSet delta_warnings = 329 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings); 330 331 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and 332 // kFileSystemWrite. 333 // TODO(sammc): Remove this. See http://crbug.com/284849. 334 if (old_warnings.find(PermissionMessage( 335 PermissionMessage::kFileSystemWriteDirectory, base::string16())) != 336 old_warnings.end()) { 337 delta_warnings.erase( 338 PermissionMessage(PermissionMessage::kFileSystemDirectory, 339 base::string16())); 340 delta_warnings.erase( 341 PermissionMessage(PermissionMessage::kFileSystemWrite, 342 base::string16())); 343 } 344 345 // It is a privilege increase if there are additional warnings present. 346 return !delta_warnings.empty(); 347} 348 349bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease( 350 const PermissionSet* old_permissions, 351 const PermissionSet* new_permissions) const { 352 if (new_permissions == NULL) 353 return false; 354 355 typedef std::set<PermissionMessage> PermissionMsgSet; 356 PermissionMsgSet old_warnings = 357 GetManifestPermissionMessages(old_permissions); 358 PermissionMsgSet new_warnings = 359 GetManifestPermissionMessages(new_permissions); 360 PermissionMsgSet delta_warnings = 361 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings); 362 363 // It is a privilege increase if there are additional warnings present. 364 return !delta_warnings.empty(); 365} 366 367bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease( 368 const PermissionSet* old_permissions, 369 const PermissionSet* new_permissions, 370 Manifest::Type extension_type) const { 371 // Platform apps host permission changes do not count as privilege increases. 372 // Note: this must remain consistent with GetHostPermissionMessages. 373 if (extension_type == Manifest::TYPE_PLATFORM_APP) 374 return false; 375 376 // If the old permission set can access any host, then it can't be elevated. 377 if (old_permissions->HasEffectiveAccessToAllHosts()) 378 return false; 379 380 // Likewise, if the new permission set has full host access, then it must be 381 // a privilege increase. 382 if (new_permissions->HasEffectiveAccessToAllHosts()) 383 return true; 384 385 const URLPatternSet& old_list = old_permissions->effective_hosts(); 386 const URLPatternSet& new_list = new_permissions->effective_hosts(); 387 388 // TODO(jstritar): This is overly conservative with respect to subdomains. 389 // For example, going from *.google.com to www.google.com will be 390 // considered an elevation, even though it is not (http://crbug.com/65337). 391 std::set<std::string> new_hosts_set( 392 permission_message_util::GetDistinctHosts(new_list, false, false)); 393 std::set<std::string> old_hosts_set( 394 permission_message_util::GetDistinctHosts(old_list, false, false)); 395 std::set<std::string> new_hosts_only = 396 base::STLSetDifference<std::set<std::string> >(new_hosts_set, 397 old_hosts_set); 398 399 return !new_hosts_only.empty(); 400} 401 402} // namespace extensions 403