// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/media/media_stream_devices_controller.h" #include "base/command_line.h" #include "base/prefs/pref_service.h" #include "base/values.h" #include "chrome/browser/content_settings/content_settings_provider.h" #include "chrome/browser/content_settings/host_content_settings_map.h" #include "chrome/browser/content_settings/tab_specific_content_settings.h" #include "chrome/browser/media/media_capture_devices_dispatcher.h" #include "chrome/browser/media/media_stream_capture_indicator.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/content_settings.h" #include "chrome/common/content_settings_pattern.h" #include "chrome/common/pref_names.h" #include "components/user_prefs/pref_registry_syncable.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/media_stream_request.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/user_manager.h" #endif using content::BrowserThread; namespace { bool HasAnyAvailableDevice() { const content::MediaStreamDevices& audio_devices = MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices(); const content::MediaStreamDevices& video_devices = MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices(); return !audio_devices.empty() || !video_devices.empty(); } bool IsInKioskMode() { if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) return true; #if defined(OS_CHROMEOS) const chromeos::UserManager* user_manager = chromeos::UserManager::Get(); return user_manager && user_manager->IsLoggedInAsKioskApp(); #else return false; #endif } } // namespace MediaStreamDevicesController::MediaStreamDevicesController( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) : web_contents_(web_contents), request_(request), callback_(callback), microphone_requested_( request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE), webcam_requested_( request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents); // Don't call GetDevicePolicy from the initializer list since the // implementation depends on member variables. if (microphone_requested_ && GetDevicePolicy(prefs::kAudioCaptureAllowed, prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) { microphone_requested_ = false; } if (webcam_requested_ && GetDevicePolicy(prefs::kVideoCaptureAllowed, prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) { webcam_requested_ = false; } } MediaStreamDevicesController::~MediaStreamDevicesController() {} // static void MediaStreamDevicesController::RegisterUserPrefs( user_prefs::PrefRegistrySyncable* prefs) { prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); } bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() { // If this is a no UI check for policies only go straight to accept - policy // check will be done automatically on the way. if (request_.request_type == content::MEDIA_OPEN_DEVICE) { Accept(false); return true; } // Tab capture is allowed for extensions only and infobar is not shown for // extensions. if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE || request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) { Deny(false); return true; } // Deny the request if the security origin is empty, this happens with // file access without |--allow-file-access-from-files| flag. if (request_.security_origin.is_empty()) { Deny(false); return true; } // Deny the request if there is no device attached to the OS. if (!HasAnyAvailableDevice()) { Deny(false); return true; } // Check if any allow exception has been made for this request. if (IsRequestAllowedByDefault()) { Accept(false); return true; } // Check if any block exception has been made for this request. if (IsRequestBlockedByDefault()) { Deny(false); return true; } // Check if the media default setting is set to block. if (IsDefaultMediaAccessBlocked()) { Deny(false); return true; } // Show the infobar. return false; } const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const { return request_.security_origin.spec(); } void MediaStreamDevicesController::Accept(bool update_content_setting) { if (content_settings_) content_settings_->OnMediaStreamAllowed(); // Get the default devices for the request. content::MediaStreamDevices devices; if (microphone_requested_ || webcam_requested_) { switch (request_.request_type) { case content::MEDIA_OPEN_DEVICE: // For open device request pick the desired device or fall back to the // first available of the given type. MediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice( request_.requested_device_id, microphone_requested_, webcam_requested_, &devices); break; case content::MEDIA_DEVICE_ACCESS: case content::MEDIA_GENERATE_STREAM: case content::MEDIA_ENUMERATE_DEVICES: // Get the default devices for the request. MediaCaptureDevicesDispatcher::GetInstance()-> GetDefaultDevicesForProfile(profile_, microphone_requested_, webcam_requested_, &devices); break; } if (update_content_setting && IsSchemeSecure() && !devices.empty()) SetPermission(true); } scoped_ptr ui; if (!devices.empty()) { ui = MediaCaptureDevicesDispatcher::GetInstance()-> GetMediaStreamCaptureIndicator()->RegisterMediaStream( web_contents_, devices); } callback_.Run(devices, ui.Pass()); } void MediaStreamDevicesController::Deny(bool update_content_setting) { // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. if (content_settings_) { content_settings_->OnContentBlocked(CONTENT_SETTINGS_TYPE_MEDIASTREAM, std::string()); } if (update_content_setting) SetPermission(false); callback_.Run(content::MediaStreamDevices(), scoped_ptr()); } MediaStreamDevicesController::DevicePolicy MediaStreamDevicesController::GetDevicePolicy( const char* policy_name, const char* whitelist_policy_name) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // If the security origin policy matches a value in the whitelist, allow it. // Otherwise, check the |policy_name| master switch for the default behavior. PrefService* prefs = profile_->GetPrefs(); // TODO(tommi): Remove the kiosk mode check when the whitelist below // is visible in the media exceptions UI. // See discussion here: https://codereview.chromium.org/15738004/ if (IsInKioskMode()) { const base::ListValue* list = prefs->GetList(whitelist_policy_name); std::string value; for (size_t i = 0; i < list->GetSize(); ++i) { if (list->GetString(i, &value)) { ContentSettingsPattern pattern = ContentSettingsPattern::FromString(value); if (pattern == ContentSettingsPattern::Wildcard()) { DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value; continue; } DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value; if (pattern.IsValid() && pattern.Matches(request_.security_origin)) return ALWAYS_ALLOW; } } } // If a match was not found, check if audio capture is otherwise disallowed // or if the user should be prompted. Setting the policy value to "true" // is equal to not setting it at all, so from hereon out, we will return // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access). if (!prefs->GetBoolean(policy_name)) return ALWAYS_DENY; return POLICY_NOT_SET; } bool MediaStreamDevicesController::IsRequestAllowedByDefault() const { // The request from internal objects like chrome://URLs is always allowed. if (ShouldAlwaysAllowOrigin()) return true; struct { bool has_capability; const char* policy_name; const char* list_policy_name; ContentSettingsType settings_type; } device_checks[] = { { microphone_requested_, prefs::kAudioCaptureAllowed, prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC }, { webcam_requested_, prefs::kVideoCaptureAllowed, prefs::kVideoCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA }, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) { if (!device_checks[i].has_capability) continue; DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name, device_checks[i].list_policy_name); if (policy == ALWAYS_DENY || (policy == POLICY_NOT_SET && profile_->GetHostContentSettingsMap()->GetContentSetting( request_.security_origin, request_.security_origin, device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) != CONTENT_SETTING_ALLOW)) { return false; } // If we get here, then either policy is set to ALWAYS_ALLOW or the content // settings allow the request by default. } return true; } bool MediaStreamDevicesController::IsRequestBlockedByDefault() const { if (microphone_requested_ && profile_->GetHostContentSettingsMap()->GetContentSetting( request_.security_origin, request_.security_origin, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, NO_RESOURCE_IDENTIFIER) != CONTENT_SETTING_BLOCK) { return false; } if (webcam_requested_ && profile_->GetHostContentSettingsMap()->GetContentSetting( request_.security_origin, request_.security_origin, CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, NO_RESOURCE_IDENTIFIER) != CONTENT_SETTING_BLOCK) { return false; } return true; } bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. ContentSetting current_setting = profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL); return (current_setting == CONTENT_SETTING_BLOCK); } bool MediaStreamDevicesController::IsSchemeSecure() const { return (request_.security_origin.SchemeIsSecure()); } bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const { // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA. return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent( request_.security_origin, request_.security_origin, CONTENT_SETTINGS_TYPE_MEDIASTREAM); } void MediaStreamDevicesController::SetPermission(bool allowed) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); #if defined(OS_ANDROID) // We do not support sticky operations on Android yet. return; #endif ContentSettingsPattern primary_pattern = ContentSettingsPattern::FromURLNoWildcard(request_.security_origin); // Check the pattern is valid or not. When the request is from a file access, // no exception will be made. if (!primary_pattern.IsValid()) return; ContentSetting content_setting = allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; if (microphone_requested_) { profile_->GetHostContentSettingsMap()->SetContentSetting( primary_pattern, ContentSettingsPattern::Wildcard(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, std::string(), content_setting); } if (webcam_requested_) { profile_->GetHostContentSettingsMap()->SetContentSetting( primary_pattern, ContentSettingsPattern::Wildcard(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, std::string(), content_setting); } }