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
30using content::BrowserThread;
31
32namespace {
33
34bool 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
43bool 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
57MediaStreamDevicesController::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
90MediaStreamDevicesController::~MediaStreamDevicesController() {
91  if (!callback_.is_null()) {
92    callback_.Run(content::MediaStreamDevices(),
93                  scoped_ptr<content::MediaStreamUI>());
94  }
95}
96
97// static
98void 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
113bool 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
158const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
159  return request_.security_origin.spec();
160}
161
162void 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
264void 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
275MediaStreamDevicesController::DevicePolicy
276MediaStreamDevicesController::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
317bool 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
366int 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
391bool 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
402bool MediaStreamDevicesController::IsSchemeSecure() const {
403  return (request_.security_origin.SchemeIsSecure());
404}
405
406bool 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
415void 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
448void 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
476void 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