1 // Copyright (c) 2012 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/browser/media/media_stream_devices_controller.h" 6 7 #include "base/command_line.h" 8 #include "base/prefs/pref_service.h" 9 #include "base/values.h" 10 #include "chrome/browser/content_settings/content_settings_provider.h" 11 #include "chrome/browser/content_settings/host_content_settings_map.h" 12 #include "chrome/browser/content_settings/tab_specific_content_settings.h" 13 #include "chrome/browser/media/media_capture_devices_dispatcher.h" 14 #include "chrome/browser/media/media_stream_capture_indicator.h" 15 #include "chrome/browser/prefs/scoped_user_pref_update.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/ui/browser.h" 18 #include "chrome/common/chrome_switches.h" 19 #include "chrome/common/content_settings.h" 20 #include "chrome/common/content_settings_pattern.h" 21 #include "chrome/common/pref_names.h" 22 #include "components/user_prefs/pref_registry_syncable.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "content/public/common/media_stream_request.h" 25 26 #if defined(OS_CHROMEOS) 27 #include "chrome/browser/chromeos/login/user_manager.h" 28 #endif 29 30 using content::BrowserThread; 31 32 namespace { 33 34 bool HasAnyAvailableDevice() { 35 const content::MediaStreamDevices& audio_devices = 36 MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices(); 37 const content::MediaStreamDevices& video_devices = 38 MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices(); 39 40 return !audio_devices.empty() || !video_devices.empty(); 41 } 42 43 bool IsInKioskMode() { 44 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) 45 return true; 46 47 #if defined(OS_CHROMEOS) 48 const chromeos::UserManager* user_manager = chromeos::UserManager::Get(); 49 return user_manager && user_manager->IsLoggedInAsKioskApp(); 50 #else 51 return false; 52 #endif 53 } 54 55 } // namespace 56 57 MediaStreamDevicesController::MediaStreamDevicesController( 58 content::WebContents* web_contents, 59 const content::MediaStreamRequest& request, 60 const content::MediaResponseCallback& callback) 61 : web_contents_(web_contents), 62 request_(request), 63 callback_(callback), 64 // For MEDIA_OPEN_DEVICE requests (Pepper) we always request both webcam 65 // and microphone to avoid popping two infobars. 66 microphone_requested_( 67 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE || 68 request.request_type == content::MEDIA_OPEN_DEVICE), 69 webcam_requested_( 70 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE || 71 request.request_type == content::MEDIA_OPEN_DEVICE) { 72 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 73 content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents); 74 75 // Don't call GetDevicePolicy from the initializer list since the 76 // implementation depends on member variables. 77 if (microphone_requested_ && 78 GetDevicePolicy(prefs::kAudioCaptureAllowed, 79 prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) { 80 microphone_requested_ = false; 81 } 82 83 if (webcam_requested_ && 84 GetDevicePolicy(prefs::kVideoCaptureAllowed, 85 prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) { 86 webcam_requested_ = false; 87 } 88 } 89 90 MediaStreamDevicesController::~MediaStreamDevicesController() { 91 if (!callback_.is_null()) { 92 callback_.Run(content::MediaStreamDevices(), 93 scoped_ptr<content::MediaStreamUI>()); 94 } 95 } 96 97 // static 98 void MediaStreamDevicesController::RegisterProfilePrefs( 99 user_prefs::PrefRegistrySyncable* prefs) { 100 prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, 101 true, 102 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 103 prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, 104 true, 105 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 106 prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls, 107 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 108 prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls, 109 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 110 } 111 112 113 bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() { 114 // Tab capture is allowed for extensions only and infobar is not shown for 115 // extensions. 116 if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE || 117 request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) { 118 Deny(false); 119 return true; 120 } 121 122 // Deny the request if the security origin is empty, this happens with 123 // file access without |--allow-file-access-from-files| flag. 124 if (request_.security_origin.is_empty()) { 125 Deny(false); 126 return true; 127 } 128 129 // Deny the request if there is no device attached to the OS. 130 if (!HasAnyAvailableDevice()) { 131 Deny(false); 132 return true; 133 } 134 135 // Check if any allow exception has been made for this request. 136 if (IsRequestAllowedByDefault()) { 137 Accept(false); 138 return true; 139 } 140 141 // Filter any parts of the request that have been blocked by default and deny 142 // it if nothing is left to accept. 143 if (FilterBlockedByDefaultDevices() == 0) { 144 Deny(false); 145 return true; 146 } 147 148 // Check if the media default setting is set to block. 149 if (IsDefaultMediaAccessBlocked()) { 150 Deny(false); 151 return true; 152 } 153 154 // Show the infobar. 155 return false; 156 } 157 158 const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const { 159 return request_.security_origin.spec(); 160 } 161 162 void MediaStreamDevicesController::Accept(bool update_content_setting) { 163 NotifyUIRequestAccepted(); 164 165 // Get the default devices for the request. 166 content::MediaStreamDevices devices; 167 if (microphone_requested_ || webcam_requested_) { 168 switch (request_.request_type) { 169 case content::MEDIA_OPEN_DEVICE: { 170 const content::MediaStreamDevice* device = NULL; 171 // For open device request pick the desired device or fall back to the 172 // first available of the given type. 173 if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { 174 device = MediaCaptureDevicesDispatcher::GetInstance()-> 175 GetRequestedAudioDevice(request_.requested_audio_device_id); 176 // TODO(wjia): Confirm this is the intended behavior. 177 if (!device) { 178 device = MediaCaptureDevicesDispatcher::GetInstance()-> 179 GetFirstAvailableAudioDevice(); 180 } 181 } else if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { 182 // Pepper API opens only one device at a time. 183 device = MediaCaptureDevicesDispatcher::GetInstance()-> 184 GetRequestedVideoDevice(request_.requested_video_device_id); 185 // TODO(wjia): Confirm this is the intended behavior. 186 if (!device) { 187 device = MediaCaptureDevicesDispatcher::GetInstance()-> 188 GetFirstAvailableVideoDevice(); 189 } 190 } 191 if (device) 192 devices.push_back(*device); 193 break; 194 } case content::MEDIA_GENERATE_STREAM: { 195 bool needs_audio_device = microphone_requested_; 196 bool needs_video_device = webcam_requested_; 197 198 // Get the exact audio or video device if an id is specified. 199 if (!request_.requested_audio_device_id.empty()) { 200 const content::MediaStreamDevice* audio_device = 201 MediaCaptureDevicesDispatcher::GetInstance()-> 202 GetRequestedAudioDevice(request_.requested_audio_device_id); 203 if (audio_device) { 204 devices.push_back(*audio_device); 205 needs_audio_device = false; 206 } 207 } 208 if (!request_.requested_video_device_id.empty()) { 209 const content::MediaStreamDevice* video_device = 210 MediaCaptureDevicesDispatcher::GetInstance()-> 211 GetRequestedVideoDevice(request_.requested_video_device_id); 212 if (video_device) { 213 devices.push_back(*video_device); 214 needs_video_device = false; 215 } 216 } 217 218 // If either or both audio and video devices were requested but not 219 // specified by id, get the default devices. 220 if (needs_audio_device || needs_video_device) { 221 MediaCaptureDevicesDispatcher::GetInstance()-> 222 GetDefaultDevicesForProfile(profile_, 223 needs_audio_device, 224 needs_video_device, 225 &devices); 226 } 227 break; 228 } case content::MEDIA_DEVICE_ACCESS: 229 // Get the default devices for the request. 230 MediaCaptureDevicesDispatcher::GetInstance()-> 231 GetDefaultDevicesForProfile(profile_, 232 microphone_requested_, 233 webcam_requested_, 234 &devices); 235 break; 236 case content::MEDIA_ENUMERATE_DEVICES: 237 // Do nothing. 238 NOTREACHED(); 239 break; 240 } 241 242 // TODO(raymes): We currently set the content permission for non-https 243 // websites for Pepper requests as well. This is temporary and should be 244 // removed. 245 if (update_content_setting) { 246 if ((IsSchemeSecure() && !devices.empty()) || 247 request_.request_type == content::MEDIA_OPEN_DEVICE) { 248 SetPermission(true); 249 } 250 } 251 } 252 253 scoped_ptr<content::MediaStreamUI> ui; 254 if (!devices.empty()) { 255 ui = MediaCaptureDevicesDispatcher::GetInstance()-> 256 GetMediaStreamCaptureIndicator()->RegisterMediaStream( 257 web_contents_, devices); 258 } 259 content::MediaResponseCallback cb = callback_; 260 callback_.Reset(); 261 cb.Run(devices, ui.Pass()); 262 } 263 264 void MediaStreamDevicesController::Deny(bool update_content_setting) { 265 NotifyUIRequestDenied(); 266 267 if (update_content_setting) 268 SetPermission(false); 269 270 content::MediaResponseCallback cb = callback_; 271 callback_.Reset(); 272 cb.Run(content::MediaStreamDevices(), scoped_ptr<content::MediaStreamUI>()); 273 } 274 275 MediaStreamDevicesController::DevicePolicy 276 MediaStreamDevicesController::GetDevicePolicy( 277 const char* policy_name, 278 const char* whitelist_policy_name) const { 279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 280 281 // If the security origin policy matches a value in the whitelist, allow it. 282 // Otherwise, check the |policy_name| master switch for the default behavior. 283 284 PrefService* prefs = profile_->GetPrefs(); 285 286 // TODO(tommi): Remove the kiosk mode check when the whitelist below 287 // is visible in the media exceptions UI. 288 // See discussion here: https://codereview.chromium.org/15738004/ 289 if (IsInKioskMode()) { 290 const base::ListValue* list = prefs->GetList(whitelist_policy_name); 291 std::string value; 292 for (size_t i = 0; i < list->GetSize(); ++i) { 293 if (list->GetString(i, &value)) { 294 ContentSettingsPattern pattern = 295 ContentSettingsPattern::FromString(value); 296 if (pattern == ContentSettingsPattern::Wildcard()) { 297 DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value; 298 continue; 299 } 300 DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value; 301 if (pattern.IsValid() && pattern.Matches(request_.security_origin)) 302 return ALWAYS_ALLOW; 303 } 304 } 305 } 306 307 // If a match was not found, check if audio capture is otherwise disallowed 308 // or if the user should be prompted. Setting the policy value to "true" 309 // is equal to not setting it at all, so from hereon out, we will return 310 // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access). 311 if (!prefs->GetBoolean(policy_name)) 312 return ALWAYS_DENY; 313 314 return POLICY_NOT_SET; 315 } 316 317 bool MediaStreamDevicesController::IsRequestAllowedByDefault() const { 318 // The request from internal objects like chrome://URLs is always allowed. 319 if (ShouldAlwaysAllowOrigin()) 320 return true; 321 322 struct { 323 bool has_capability; 324 const char* policy_name; 325 const char* list_policy_name; 326 ContentSettingsType settings_type; 327 } device_checks[] = { 328 { microphone_requested_, prefs::kAudioCaptureAllowed, 329 prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC }, 330 { webcam_requested_, prefs::kVideoCaptureAllowed, 331 prefs::kVideoCaptureAllowedUrls, 332 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA }, 333 }; 334 335 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) { 336 if (!device_checks[i].has_capability) 337 continue; 338 339 DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name, 340 device_checks[i].list_policy_name); 341 342 if (policy == ALWAYS_DENY) 343 return false; 344 345 if (policy == POLICY_NOT_SET) { 346 // Only load content settings from secure origins unless it is a 347 // content::MEDIA_OPEN_DEVICE (Pepper) request. 348 if (!IsSchemeSecure() && 349 request_.request_type != content::MEDIA_OPEN_DEVICE) { 350 return false; 351 } 352 if (profile_->GetHostContentSettingsMap()->GetContentSetting( 353 request_.security_origin, request_.security_origin, 354 device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) != 355 CONTENT_SETTING_ALLOW) { 356 return false; 357 } 358 } 359 // If we get here, then either policy is set to ALWAYS_ALLOW or the content 360 // settings allow the request by default. 361 } 362 363 return true; 364 } 365 366 int MediaStreamDevicesController::FilterBlockedByDefaultDevices() { 367 int requested_devices = microphone_requested_ + webcam_requested_; 368 if (microphone_requested_ && 369 profile_->GetHostContentSettingsMap()->GetContentSetting( 370 request_.security_origin, 371 request_.security_origin, 372 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, 373 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) { 374 requested_devices--; 375 microphone_requested_ = false; 376 } 377 378 if (webcam_requested_ && 379 profile_->GetHostContentSettingsMap()->GetContentSetting( 380 request_.security_origin, 381 request_.security_origin, 382 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, 383 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) { 384 requested_devices--; 385 webcam_requested_ = false; 386 } 387 388 return requested_devices; 389 } 390 391 bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const { 392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 393 // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the 394 // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and 395 // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. 396 ContentSetting current_setting = 397 profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( 398 CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL); 399 return (current_setting == CONTENT_SETTING_BLOCK); 400 } 401 402 bool MediaStreamDevicesController::IsSchemeSecure() const { 403 return (request_.security_origin.SchemeIsSecure()); 404 } 405 406 bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const { 407 // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the 408 // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and 409 // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. 410 return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent( 411 request_.security_origin, request_.security_origin, 412 CONTENT_SETTINGS_TYPE_MEDIASTREAM); 413 } 414 415 void MediaStreamDevicesController::SetPermission(bool allowed) const { 416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 417 #if defined(OS_ANDROID) 418 // We do not support sticky operations on Android yet. 419 return; 420 #endif 421 ContentSettingsPattern primary_pattern = 422 ContentSettingsPattern::FromURLNoWildcard(request_.security_origin); 423 // Check the pattern is valid or not. When the request is from a file access, 424 // no exception will be made. 425 if (!primary_pattern.IsValid()) 426 return; 427 428 ContentSetting content_setting = allowed ? 429 CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; 430 if (microphone_requested_) { 431 profile_->GetHostContentSettingsMap()->SetContentSetting( 432 primary_pattern, 433 ContentSettingsPattern::Wildcard(), 434 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, 435 std::string(), 436 content_setting); 437 } 438 if (webcam_requested_) { 439 profile_->GetHostContentSettingsMap()->SetContentSetting( 440 primary_pattern, 441 ContentSettingsPattern::Wildcard(), 442 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, 443 std::string(), 444 content_setting); 445 } 446 } 447 448 void MediaStreamDevicesController::NotifyUIRequestAccepted() const { 449 if (!content_settings_) 450 return; 451 452 // We need to figure out which part of the request is accepted or denied here. 453 // For example, when the request contains both audio and video, but audio is 454 // blocked by the policy, then we will prompt the infobar to ask for video 455 // permission. In case the users approve the permission, 456 // we need to show an allowed icon for video but blocked icon for audio. 457 if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { 458 // The request might contain audio while |webcam_requested_| is false, 459 // this happens when the policy is blocking the audio. 460 if (microphone_requested_) 461 content_settings_->OnMicrophoneAccessed(); 462 else 463 content_settings_->OnMicrophoneAccessBlocked(); 464 } 465 466 if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { 467 // The request might contain video while |webcam_requested_| is false, 468 // this happens when the policy is blocking the video. 469 if (webcam_requested_) 470 content_settings_->OnCameraAccessed(); 471 else 472 content_settings_->OnCameraAccessBlocked(); 473 } 474 } 475 476 void MediaStreamDevicesController::NotifyUIRequestDenied() const { 477 if (!content_settings_) 478 return; 479 480 // Do not show the block icons for tab capture. 481 if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE || 482 request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) { 483 return; 484 } 485 486 if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) 487 content_settings_->OnMicrophoneAccessBlocked(); 488 if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) 489 content_settings_->OnCameraAccessBlocked(); 490 } 491