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