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