Home | History | Annotate | Download | only in media
      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