chrome_permission_message_provider.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 "chrome/grit/generated_resources.h" 10#include "extensions/common/extensions_client.h" 11#include "extensions/common/permissions/permission_message.h" 12#include "extensions/common/permissions/permission_message_util.h" 13#include "extensions/common/permissions/permission_set.h" 14#include "extensions/common/url_pattern.h" 15#include "extensions/common/url_pattern_set.h" 16#include "grit/extensions_strings.h" 17#include "ui/base/l10n/l10n_util.h" 18#include "url/gurl.h" 19 20namespace extensions { 21 22namespace { 23 24typedef std::set<PermissionMessage> PermissionMsgSet; 25 26template<typename T> 27typename T::iterator FindMessageByID(T& messages, int id) { 28 for (typename T::iterator it = messages.begin(); 29 it != messages.end(); ++it) { 30 if (it->id() == id) 31 return it; 32 } 33 return messages.end(); 34} 35 36template<typename T> 37typename T::const_iterator FindMessageByID(const T& messages, int id) { 38 for (typename T::const_iterator it = messages.begin(); 39 it != messages.end(); ++it) { 40 if (it->id() == id) 41 return it; 42 } 43 return messages.end(); 44} 45 46template<typename T> 47void SuppressMessage(T& messages, 48 int suppressing_message, 49 int suppressed_message) { 50 typename T::iterator suppressed = FindMessageByID(messages, 51 suppressed_message); 52 if (suppressed != messages.end() && 53 FindMessageByID(messages, suppressing_message) != messages.end()) { 54 messages.erase(suppressed); 55 } 56} 57 58bool ContainsMessages(const PermissionMessages& messages, 59 int first_message, 60 int second_message) { 61 return FindMessageByID(messages, first_message) != messages.end() && 62 FindMessageByID(messages, second_message) != messages.end(); 63} 64 65bool ContainsMessages(const PermissionMessages& messages, 66 int first_message, 67 int second_message, 68 int third_message) { 69 return ContainsMessages(messages, first_message, second_message) && 70 FindMessageByID(messages, third_message) != messages.end(); 71} 72 73} // namespace 74 75ChromePermissionMessageProvider::ChromePermissionMessageProvider() { 76} 77 78ChromePermissionMessageProvider::~ChromePermissionMessageProvider() { 79} 80 81PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages( 82 const PermissionSet* permissions, 83 Manifest::Type extension_type) const { 84 PermissionMessages messages; 85 if (permissions->HasEffectiveFullAccess()) { 86 messages.push_back(PermissionMessage( 87 PermissionMessage::kFullAccess, 88 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS))); 89 return messages; 90 } 91 92 // Some warnings are more generic and/or powerful and superseed other 93 // warnings. In that case, the first message suppresses the second one. 94 std::multimap<PermissionMessage::ID, PermissionMessage::ID> kSuppressList; 95 kSuppressList.insert( 96 {PermissionMessage::kBluetooth, PermissionMessage::kBluetoothDevices}); 97 kSuppressList.insert( 98 {PermissionMessage::kBookmarks, PermissionMessage::kOverrideBookmarksUI}); 99 // History already allows reading favicons. 100 kSuppressList.insert( 101 {PermissionMessage::kBrowsingHistory, PermissionMessage::kFavicon}); 102 // History already allows tabs access. 103 kSuppressList.insert( 104 {PermissionMessage::kBrowsingHistory, PermissionMessage::kTabs}); 105 // History already allows access the list of most frequently visited sites. 106 kSuppressList.insert( 107 {PermissionMessage::kBrowsingHistory, PermissionMessage::kTopSites}); 108 // A special hack: If kFileSystemWriteDirectory would be displayed, hide 109 // kFileSystemDirectory as the write directory message implies it. 110 // TODO(sammc): Remove this. See http://crbug.com/284849. 111 kSuppressList.insert({PermissionMessage::kFileSystemWriteDirectory, 112 PermissionMessage::kFileSystemDirectory}); 113 // Full access already allows DeclarativeWebRequest. 114 kSuppressList.insert({PermissionMessage::kHostsAll, 115 PermissionMessage::kDeclarativeWebRequest}); 116 // Full access implies reading the list of most frequently visited sites. 117 kSuppressList.insert( 118 {PermissionMessage::kHostsAll, PermissionMessage::kTopSites}); 119 // Full access already covers tabs access. 120 kSuppressList.insert( 121 {PermissionMessage::kHostsAll, PermissionMessage::kTabs}); 122 // Tabs already allows reading favicons. 123 kSuppressList.insert({PermissionMessage::kTabs, PermissionMessage::kFavicon}); 124 // Tabs already allows reading the list of most frequently visited sites. 125 kSuppressList.insert( 126 {PermissionMessage::kTabs, PermissionMessage::kTopSites}); 127 128 PermissionMsgSet host_msgs = 129 GetHostPermissionMessages(permissions, extension_type); 130 PermissionMsgSet api_msgs = GetAPIPermissionMessages(permissions); 131 PermissionMsgSet manifest_permission_msgs = 132 GetManifestPermissionMessages(permissions); 133 messages.insert(messages.end(), host_msgs.begin(), host_msgs.end()); 134 messages.insert(messages.end(), api_msgs.begin(), api_msgs.end()); 135 messages.insert(messages.end(), manifest_permission_msgs.begin(), 136 manifest_permission_msgs.end()); 137 138 for (std::multimap<PermissionMessage::ID, 139 PermissionMessage::ID>::const_iterator it = 140 kSuppressList.begin(); 141 it != kSuppressList.end(); 142 ++it) { 143 SuppressMessage(messages, it->first, it->second); 144 } 145 146 return messages; 147} 148 149std::vector<base::string16> ChromePermissionMessageProvider::GetWarningMessages( 150 const PermissionSet* permissions, 151 Manifest::Type extension_type) const { 152 std::vector<base::string16> message_strings; 153 PermissionMessages messages = 154 GetPermissionMessages(permissions, extension_type); 155 156 for (PermissionMessages::const_iterator i = messages.begin(); 157 i != messages.end(); ++i) { 158 int id = i->id(); 159 // Access to users' devices should provide a single warning message 160 // specifying the transport method used; USB, serial and/or Bluetooth. 161 if (id == PermissionMessage::kBluetooth || 162 id == PermissionMessage::kSerial || 163 id == PermissionMessage::kUsb) { 164 if (ContainsMessages(messages, 165 PermissionMessage::kBluetooth, 166 PermissionMessage::kSerial, 167 PermissionMessage::kUsb)) { 168 if (id == PermissionMessage::kBluetooth) { 169 message_strings.push_back(l10n_util::GetStringUTF16( 170 IDS_EXTENSION_PROMPT_WARNING_ALL_DEVICES)); 171 } 172 continue; 173 } 174 if (ContainsMessages(messages, 175 PermissionMessage::kBluetooth, 176 PermissionMessage::kUsb)) { 177 if (id == PermissionMessage::kBluetooth) { 178 message_strings.push_back(l10n_util::GetStringUTF16( 179 IDS_EXTENSION_PROMPT_WARNING_USB_BLUETOOTH)); 180 } 181 continue; 182 } 183 if (ContainsMessages(messages, 184 PermissionMessage::kSerial, 185 PermissionMessage::kUsb)) { 186 if (id == PermissionMessage::kSerial) { 187 message_strings.push_back(l10n_util::GetStringUTF16( 188 IDS_EXTENSION_PROMPT_WARNING_USB_SERIAL)); 189 } 190 continue; 191 } 192 if (ContainsMessages(messages, 193 PermissionMessage::kBluetooth, 194 PermissionMessage::kSerial)) { 195 if (id == PermissionMessage::kBluetooth) { 196 message_strings.push_back(l10n_util::GetStringUTF16( 197 IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH_SERIAL)); 198 } 199 continue; 200 } 201 } 202 if (id == PermissionMessage::kAccessibilityFeaturesModify || 203 id == PermissionMessage::kAccessibilityFeaturesRead) { 204 if (ContainsMessages(messages, 205 PermissionMessage::kAccessibilityFeaturesModify, 206 PermissionMessage::kAccessibilityFeaturesRead)) { 207 if (id == PermissionMessage::kAccessibilityFeaturesModify) { 208 message_strings.push_back(l10n_util::GetStringUTF16( 209 IDS_EXTENSION_PROMPT_WARNING_ACCESSIBILITY_FEATURES_READ_MODIFY)); 210 } 211 continue; 212 } 213 } 214 if (id == PermissionMessage::kAudioCapture || 215 id == PermissionMessage::kVideoCapture) { 216 if (ContainsMessages(messages, 217 PermissionMessage::kAudioCapture, 218 PermissionMessage::kVideoCapture)) { 219 if (id == PermissionMessage::kAudioCapture) { 220 message_strings.push_back(l10n_util::GetStringUTF16( 221 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE)); 222 } 223 continue; 224 } 225 } 226 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo || 227 id == PermissionMessage::kMediaGalleriesAllGalleriesDelete || 228 id == PermissionMessage::kMediaGalleriesAllGalleriesRead) { 229 if (ContainsMessages( 230 messages, 231 PermissionMessage::kMediaGalleriesAllGalleriesCopyTo, 232 PermissionMessage::kMediaGalleriesAllGalleriesDelete, 233 PermissionMessage::kMediaGalleriesAllGalleriesRead)) { 234 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) { 235 message_strings.push_back(l10n_util::GetStringUTF16( 236 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE_DELETE)); 237 } 238 continue; 239 } 240 if (ContainsMessages( 241 messages, 242 PermissionMessage::kMediaGalleriesAllGalleriesCopyTo, 243 PermissionMessage::kMediaGalleriesAllGalleriesRead)) { 244 if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) { 245 message_strings.push_back(l10n_util::GetStringUTF16( 246 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE)); 247 } 248 continue; 249 } 250 if (ContainsMessages( 251 messages, 252 PermissionMessage::kMediaGalleriesAllGalleriesDelete, 253 PermissionMessage::kMediaGalleriesAllGalleriesRead)) { 254 if (id == PermissionMessage::kMediaGalleriesAllGalleriesDelete) { 255 message_strings.push_back(l10n_util::GetStringUTF16( 256 IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_DELETE)); 257 } 258 continue; 259 } 260 } 261 if (permissions->HasAPIPermission(APIPermission::kSessions) && 262 id == PermissionMessage::kTabs) { 263 message_strings.push_back(l10n_util::GetStringUTF16( 264 IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_AND_SESSIONS)); 265 continue; 266 } 267 if (permissions->HasAPIPermission(APIPermission::kSessions) && 268 id == PermissionMessage::kBrowsingHistory) { 269 message_strings.push_back(l10n_util::GetStringUTF16( 270 IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE_AND_SESSIONS)); 271 continue; 272 } 273 274 message_strings.push_back(i->message()); 275 } 276 277 return message_strings; 278} 279 280std::vector<base::string16> 281ChromePermissionMessageProvider::GetWarningMessagesDetails( 282 const PermissionSet* permissions, 283 Manifest::Type extension_type) const { 284 std::vector<base::string16> message_strings; 285 PermissionMessages messages = 286 GetPermissionMessages(permissions, extension_type); 287 288 for (PermissionMessages::const_iterator i = messages.begin(); 289 i != messages.end(); ++i) 290 message_strings.push_back(i->details()); 291 292 return message_strings; 293} 294 295bool ChromePermissionMessageProvider::IsPrivilegeIncrease( 296 const PermissionSet* old_permissions, 297 const PermissionSet* new_permissions, 298 Manifest::Type extension_type) const { 299 // Things can't get worse than native code access. 300 if (old_permissions->HasEffectiveFullAccess()) 301 return false; 302 303 // Otherwise, it's a privilege increase if the new one has full access. 304 if (new_permissions->HasEffectiveFullAccess()) 305 return true; 306 307 if (IsHostPrivilegeIncrease(old_permissions, new_permissions, extension_type)) 308 return true; 309 310 if (IsAPIPrivilegeIncrease(old_permissions, new_permissions)) 311 return true; 312 313 if (IsManifestPermissionPrivilegeIncrease(old_permissions, new_permissions)) 314 return true; 315 316 return false; 317} 318 319std::set<PermissionMessage> 320ChromePermissionMessageProvider::GetAPIPermissionMessages( 321 const PermissionSet* permissions) const { 322 PermissionMsgSet messages; 323 for (APIPermissionSet::const_iterator permission_it = 324 permissions->apis().begin(); 325 permission_it != permissions->apis().end(); ++permission_it) { 326 if (permission_it->HasMessages()) { 327 PermissionMessages new_messages = permission_it->GetMessages(); 328 messages.insert(new_messages.begin(), new_messages.end()); 329 } 330 } 331 332 // A special hack: The warning message for declarativeWebRequest 333 // permissions speaks about blocking parts of pages, which is a 334 // subset of what the "<all_urls>" access allows. Therefore we 335 // display only the "<all_urls>" warning message if both permissions 336 // are required. 337 if (permissions->ShouldWarnAllHosts()) { 338 messages.erase( 339 PermissionMessage( 340 PermissionMessage::kDeclarativeWebRequest, base::string16())); 341 } 342 return messages; 343} 344 345std::set<PermissionMessage> 346ChromePermissionMessageProvider::GetManifestPermissionMessages( 347 const PermissionSet* permissions) const { 348 PermissionMsgSet messages; 349 for (ManifestPermissionSet::const_iterator permission_it = 350 permissions->manifest_permissions().begin(); 351 permission_it != permissions->manifest_permissions().end(); 352 ++permission_it) { 353 if (permission_it->HasMessages()) { 354 PermissionMessages new_messages = permission_it->GetMessages(); 355 messages.insert(new_messages.begin(), new_messages.end()); 356 } 357 } 358 return messages; 359} 360 361std::set<PermissionMessage> 362ChromePermissionMessageProvider::GetHostPermissionMessages( 363 const PermissionSet* permissions, 364 Manifest::Type extension_type) const { 365 PermissionMsgSet messages; 366 // Since platform apps always use isolated storage, they can't (silently) 367 // access user data on other domains, so there's no need to prompt. 368 // Note: this must remain consistent with IsHostPrivilegeIncrease. 369 // See crbug.com/255229. 370 if (extension_type == Manifest::TYPE_PLATFORM_APP) 371 return messages; 372 373 if (permissions->ShouldWarnAllHosts()) { 374 messages.insert(PermissionMessage( 375 PermissionMessage::kHostsAll, 376 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS))); 377 } else { 378 URLPatternSet regular_hosts; 379 ExtensionsClient::Get()->FilterHostPermissions( 380 permissions->effective_hosts(), ®ular_hosts, &messages); 381 382 std::set<std::string> hosts = 383 permission_message_util::GetDistinctHosts(regular_hosts, true, true); 384 if (!hosts.empty()) 385 messages.insert(permission_message_util::CreateFromHostList(hosts)); 386 } 387 return messages; 388} 389 390bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease( 391 const PermissionSet* old_permissions, 392 const PermissionSet* new_permissions) const { 393 if (new_permissions == NULL) 394 return false; 395 396 PermissionMsgSet old_warnings = GetAPIPermissionMessages(old_permissions); 397 PermissionMsgSet new_warnings = GetAPIPermissionMessages(new_permissions); 398 PermissionMsgSet delta_warnings = 399 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings); 400 401 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory. 402 // TODO(sammc): Remove this. See http://crbug.com/284849. 403 if (old_warnings.find(PermissionMessage( 404 PermissionMessage::kFileSystemWriteDirectory, base::string16())) != 405 old_warnings.end()) { 406 delta_warnings.erase( 407 PermissionMessage(PermissionMessage::kFileSystemDirectory, 408 base::string16())); 409 } 410 411 // It is a privilege increase if there are additional warnings present. 412 return !delta_warnings.empty(); 413} 414 415bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease( 416 const PermissionSet* old_permissions, 417 const PermissionSet* new_permissions) const { 418 if (new_permissions == NULL) 419 return false; 420 421 PermissionMsgSet old_warnings = 422 GetManifestPermissionMessages(old_permissions); 423 PermissionMsgSet new_warnings = 424 GetManifestPermissionMessages(new_permissions); 425 PermissionMsgSet delta_warnings = 426 base::STLSetDifference<PermissionMsgSet>(new_warnings, old_warnings); 427 428 // It is a privilege increase if there are additional warnings present. 429 return !delta_warnings.empty(); 430} 431 432bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease( 433 const PermissionSet* old_permissions, 434 const PermissionSet* new_permissions, 435 Manifest::Type extension_type) const { 436 // Platform apps host permission changes do not count as privilege increases. 437 // Note: this must remain consistent with GetHostPermissionMessages. 438 if (extension_type == Manifest::TYPE_PLATFORM_APP) 439 return false; 440 441 // If the old permission set can access any host, then it can't be elevated. 442 if (old_permissions->HasEffectiveAccessToAllHosts()) 443 return false; 444 445 // Likewise, if the new permission set has full host access, then it must be 446 // a privilege increase. 447 if (new_permissions->HasEffectiveAccessToAllHosts()) 448 return true; 449 450 const URLPatternSet& old_list = old_permissions->effective_hosts(); 451 const URLPatternSet& new_list = new_permissions->effective_hosts(); 452 453 // TODO(jstritar): This is overly conservative with respect to subdomains. 454 // For example, going from *.google.com to www.google.com will be 455 // considered an elevation, even though it is not (http://crbug.com/65337). 456 std::set<std::string> new_hosts_set( 457 permission_message_util::GetDistinctHosts(new_list, false, false)); 458 std::set<std::string> old_hosts_set( 459 permission_message_util::GetDistinctHosts(old_list, false, false)); 460 std::set<std::string> new_hosts_only = 461 base::STLSetDifference<std::set<std::string> >(new_hosts_set, 462 old_hosts_set); 463 464 return !new_hosts_only.empty(); 465} 466 467} // namespace extensions 468