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