1 // Copyright 2015 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/webrtc/permission_bubble_media_access_handler.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/metrics/field_trial.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
15 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
16 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
17 #include "chrome/browser/permissions/permission_manager_factory.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/content_settings/browser/page_specific_content_settings.h"
21 #include "components/content_settings/core/browser/host_content_settings_map.h"
22 #include "components/permissions/permission_manager.h"
23 #include "components/permissions/permission_result.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "components/prefs/pref_service.h"
26 #include "components/webrtc/media_stream_devices_controller.h"
27 #include "content/public/browser/browser_task_traits.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_types.h"
31 #include "content/public/browser/web_contents.h"
32 
33 #if defined(OS_ANDROID)
34 #include <vector>
35 
36 #include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
37 #include "components/permissions/permission_uma_util.h"
38 #include "components/permissions/permission_util.h"
39 #include "content/public/common/content_features.h"
40 #endif  // defined(OS_ANDROID)
41 
42 #if defined(OS_MAC)
43 #include "base/metrics/histogram_macros.h"
44 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
45 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
46 #include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
47 #endif
48 
49 using content::BrowserThread;
50 
51 using RepeatingMediaResponseCallback =
52     base::RepeatingCallback<void(const blink::MediaStreamDevices& devices,
53                                  blink::mojom::MediaStreamRequestResult result,
54                                  std::unique_ptr<content::MediaStreamUI> ui)>;
55 
56 #if defined(OS_MAC)
57 using system_media_permissions::SystemPermission;
58 #endif
59 
60 namespace {
61 
UpdatePageSpecificContentSettings(content::WebContents * web_contents,const content::MediaStreamRequest & request,ContentSetting audio_setting,ContentSetting video_setting)62 void UpdatePageSpecificContentSettings(
63     content::WebContents* web_contents,
64     const content::MediaStreamRequest& request,
65     ContentSetting audio_setting,
66     ContentSetting video_setting) {
67   if (!web_contents)
68     return;
69 
70   // TODO(https://crbug.com/1103176): We should extract the frame from |request|
71   auto* content_settings =
72       content_settings::PageSpecificContentSettings::GetForFrame(
73           web_contents->GetMainFrame());
74   if (!content_settings)
75     return;
76 
77   content_settings::PageSpecificContentSettings::MicrophoneCameraState
78       microphone_camera_state = content_settings::PageSpecificContentSettings::
79           MICROPHONE_CAMERA_NOT_ACCESSED;
80   std::string selected_audio_device;
81   std::string selected_video_device;
82   std::string requested_audio_device = request.requested_audio_device_id;
83   std::string requested_video_device = request.requested_video_device_id;
84 
85   // TODO(raymes): Why do we use the defaults here for the selected devices?
86   // Shouldn't we just use the devices that were actually selected?
87   Profile* profile =
88       Profile::FromBrowserContext(web_contents->GetBrowserContext());
89   if (audio_setting != CONTENT_SETTING_DEFAULT) {
90     selected_audio_device =
91         requested_audio_device.empty()
92             ? profile->GetPrefs()->GetString(prefs::kDefaultAudioCaptureDevice)
93             : requested_audio_device;
94     microphone_camera_state |=
95         content_settings::PageSpecificContentSettings::MICROPHONE_ACCESSED |
96         (audio_setting == CONTENT_SETTING_ALLOW
97              ? 0
98              : content_settings::PageSpecificContentSettings::
99                    MICROPHONE_BLOCKED);
100   }
101 
102   if (video_setting != CONTENT_SETTING_DEFAULT) {
103     selected_video_device =
104         requested_video_device.empty()
105             ? profile->GetPrefs()->GetString(prefs::kDefaultVideoCaptureDevice)
106             : requested_video_device;
107     microphone_camera_state |=
108         content_settings::PageSpecificContentSettings::CAMERA_ACCESSED |
109         (video_setting == CONTENT_SETTING_ALLOW
110              ? 0
111              : content_settings::PageSpecificContentSettings::CAMERA_BLOCKED);
112   }
113 
114   content_settings->OnMediaStreamPermissionSet(
115       PermissionManagerFactory::GetForProfile(profile)->GetCanonicalOrigin(
116           ContentSettingsType::MEDIASTREAM_CAMERA, request.security_origin,
117           web_contents->GetLastCommittedURL()),
118       microphone_camera_state, selected_audio_device, selected_video_device,
119       requested_audio_device, requested_video_device);
120 }
121 
122 }  // namespace
123 
124 struct PermissionBubbleMediaAccessHandler::PendingAccessRequest {
PendingAccessRequestPermissionBubbleMediaAccessHandler::PendingAccessRequest125   PendingAccessRequest(const content::MediaStreamRequest& request,
126                        RepeatingMediaResponseCallback callback)
127       : request(request), callback(callback) {}
~PendingAccessRequestPermissionBubbleMediaAccessHandler::PendingAccessRequest128   ~PendingAccessRequest() {}
129 
130   // TODO(gbillock): make the MediaStreamDevicesController owned by
131   // this object when we're using bubbles.
132   content::MediaStreamRequest request;
133   RepeatingMediaResponseCallback callback;
134 };
135 
PermissionBubbleMediaAccessHandler()136 PermissionBubbleMediaAccessHandler::PermissionBubbleMediaAccessHandler() {
137   // PermissionBubbleMediaAccessHandler should be created on UI thread.
138   // Otherwise, it will not receive
139   // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
140   // possible use after free.
141   DCHECK_CURRENTLY_ON(BrowserThread::UI);
142   notifications_registrar_.Add(this,
143                                content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
144                                content::NotificationService::AllSources());
145 }
146 
~PermissionBubbleMediaAccessHandler()147 PermissionBubbleMediaAccessHandler::~PermissionBubbleMediaAccessHandler() {}
148 
SupportsStreamType(content::WebContents * web_contents,const blink::mojom::MediaStreamType type,const extensions::Extension * extension)149 bool PermissionBubbleMediaAccessHandler::SupportsStreamType(
150     content::WebContents* web_contents,
151     const blink::mojom::MediaStreamType type,
152     const extensions::Extension* extension) {
153 #if defined(OS_ANDROID)
154   return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
155          type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
156          type == blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
157          type == blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
158 #else
159   return type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
160          type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
161 #endif
162 }
163 
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type,const extensions::Extension * extension)164 bool PermissionBubbleMediaAccessHandler::CheckMediaAccessPermission(
165     content::RenderFrameHost* render_frame_host,
166     const GURL& security_origin,
167     blink::mojom::MediaStreamType type,
168     const extensions::Extension* extension) {
169   content::WebContents* web_contents =
170       content::WebContents::FromRenderFrameHost(render_frame_host);
171   Profile* profile =
172       Profile::FromBrowserContext(web_contents->GetBrowserContext());
173   ContentSettingsType content_settings_type =
174       type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
175           ? ContentSettingsType::MEDIASTREAM_MIC
176           : ContentSettingsType::MEDIASTREAM_CAMERA;
177 
178   DCHECK(!security_origin.is_empty());
179   GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
180   permissions::PermissionManager* permission_manager =
181       PermissionManagerFactory::GetForProfile(profile);
182   return permission_manager
183              ->GetPermissionStatusForFrame(content_settings_type,
184                                            render_frame_host, security_origin)
185              .content_setting == CONTENT_SETTING_ALLOW;
186 }
187 
HandleRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback,const extensions::Extension * extension)188 void PermissionBubbleMediaAccessHandler::HandleRequest(
189     content::WebContents* web_contents,
190     const content::MediaStreamRequest& request,
191     content::MediaResponseCallback callback,
192     const extensions::Extension* extension) {
193   DCHECK_CURRENTLY_ON(BrowserThread::UI);
194 
195 #if defined(OS_ANDROID)
196   if (blink::IsScreenCaptureMediaType(request.video_type) &&
197       !base::FeatureList::IsEnabled(features::kUserMediaScreenCapturing)) {
198     // If screen capturing isn't enabled on Android, we'll use "invalid state"
199     // as result, same as on desktop.
200     std::move(callback).Run(
201         blink::MediaStreamDevices(),
202         blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
203     return;
204   }
205 #endif  // defined(OS_ANDROID)
206 
207   RequestsMap& requests_map = pending_requests_[web_contents];
208   requests_map.emplace(
209       next_request_id_++,
210       PendingAccessRequest(
211           request, base::AdaptCallbackForRepeating(std::move(callback))));
212 
213   // If this is the only request then show the infobar.
214   if (requests_map.size() == 1)
215     ProcessQueuedAccessRequest(web_contents);
216 }
217 
ProcessQueuedAccessRequest(content::WebContents * web_contents)218 void PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest(
219     content::WebContents* web_contents) {
220   DCHECK_CURRENTLY_ON(BrowserThread::UI);
221 
222   auto it = pending_requests_.find(web_contents);
223 
224   if (it == pending_requests_.end() || it->second.empty()) {
225     // Don't do anything if the tab was closed.
226     return;
227   }
228 
229   DCHECK(!it->second.empty());
230 
231   const int request_id = it->second.begin()->first;
232   const content::MediaStreamRequest& request =
233       it->second.begin()->second.request;
234 #if defined(OS_ANDROID)
235   if (blink::IsScreenCaptureMediaType(request.video_type)) {
236     ScreenCaptureInfoBarDelegateAndroid::Create(
237         web_contents, request,
238         base::BindOnce(
239             &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
240             base::Unretained(this), web_contents, request_id));
241     return;
242   }
243 #endif
244 
245   webrtc::MediaStreamDevicesController::RequestPermissions(
246       request, MediaCaptureDevicesDispatcher::GetInstance(),
247       base::BindOnce(
248           &PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse,
249           base::Unretained(this), web_contents, request_id, request));
250 }
251 
UpdateMediaRequestState(int render_process_id,int render_frame_id,int page_request_id,blink::mojom::MediaStreamType stream_type,content::MediaRequestState state)252 void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState(
253     int render_process_id,
254     int render_frame_id,
255     int page_request_id,
256     blink::mojom::MediaStreamType stream_type,
257     content::MediaRequestState state) {
258   DCHECK_CURRENTLY_ON(BrowserThread::UI);
259   if (state != content::MEDIA_REQUEST_STATE_CLOSING)
260     return;
261 
262   bool found = false;
263   for (auto requests_it = pending_requests_.begin();
264        requests_it != pending_requests_.end(); ++requests_it) {
265     RequestsMap& requests_map = requests_it->second;
266     for (RequestsMap::iterator it = requests_map.begin();
267          it != requests_map.end(); ++it) {
268       if (it->second.request.render_process_id == render_process_id &&
269           it->second.request.render_frame_id == render_frame_id &&
270           it->second.request.page_request_id == page_request_id) {
271         requests_map.erase(it);
272         found = true;
273         break;
274       }
275     }
276     if (found)
277       break;
278   }
279 }
280 
281 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * prefs)282 void PermissionBubbleMediaAccessHandler::RegisterProfilePrefs(
283     user_prefs::PrefRegistrySyncable* prefs) {
284   prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
285   prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
286   prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
287   prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
288 }
289 
OnMediaStreamRequestResponse(content::WebContents * web_contents,int request_id,content::MediaStreamRequest request,const blink::MediaStreamDevices & devices,blink::mojom::MediaStreamRequestResult result,bool blocked_by_feature_policy,ContentSetting audio_setting,ContentSetting video_setting)290 void PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse(
291     content::WebContents* web_contents,
292     int request_id,
293     content::MediaStreamRequest request,
294     const blink::MediaStreamDevices& devices,
295     blink::mojom::MediaStreamRequestResult result,
296     bool blocked_by_feature_policy,
297     ContentSetting audio_setting,
298     ContentSetting video_setting) {
299   if (pending_requests_.find(web_contents) == pending_requests_.end()) {
300     // WebContents has been destroyed. Don't need to do anything.
301     return;
302   }
303 
304   // If the kill switch is, or the request was blocked because of feature
305   // policy we don't update the tab context.
306   if (result != blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
307       !blocked_by_feature_policy) {
308     UpdatePageSpecificContentSettings(web_contents, request, audio_setting,
309                                       video_setting);
310   }
311 
312   std::unique_ptr<content::MediaStreamUI> ui;
313   if (!devices.empty()) {
314     ui = MediaCaptureDevicesDispatcher::GetInstance()
315              ->GetMediaStreamCaptureIndicator()
316              ->RegisterMediaStream(web_contents, devices);
317   }
318   OnAccessRequestResponse(web_contents, request_id, devices, result,
319                           std::move(ui));
320 }
321 
OnAccessRequestResponse(content::WebContents * web_contents,int request_id,const blink::MediaStreamDevices & devices,blink::mojom::MediaStreamRequestResult result,std::unique_ptr<content::MediaStreamUI> ui)322 void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse(
323     content::WebContents* web_contents,
324     int request_id,
325     const blink::MediaStreamDevices& devices,
326     blink::mojom::MediaStreamRequestResult result,
327     std::unique_ptr<content::MediaStreamUI> ui) {
328   DCHECK_CURRENTLY_ON(BrowserThread::UI);
329 
330   auto request_maps_it = pending_requests_.find(web_contents);
331   if (request_maps_it == pending_requests_.end()) {
332     // WebContents has been destroyed. Don't need to do anything.
333     return;
334   }
335 
336   RequestsMap& requests_map(request_maps_it->second);
337   if (requests_map.empty())
338     return;
339 
340   auto request_it = requests_map.find(request_id);
341   DCHECK(request_it != requests_map.end());
342   if (request_it == requests_map.end())
343     return;
344 
345   blink::mojom::MediaStreamRequestResult final_result = result;
346 
347 #if defined(OS_MAC)
348   // If the request was approved, ask for system permissions if needed, and run
349   // this function again when done.
350   if (result == blink::mojom::MediaStreamRequestResult::OK) {
351     const content::MediaStreamRequest& request = request_it->second.request;
352     if (request.audio_type ==
353         blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
354       const SystemPermission system_audio_permission =
355           system_media_permissions::CheckSystemAudioCapturePermission();
356       UMA_HISTOGRAM_ENUMERATION(
357           "Media.Audio.Capture.Mac.MicSystemPermission.UserMedia",
358           system_audio_permission);
359       if (system_audio_permission == SystemPermission::kNotDetermined) {
360         // Using WeakPtr since callback can come at any time and we might be
361         // destroyed.
362         system_media_permissions::RequestSystemAudioCapturePermisson(
363             base::BindOnce(
364                 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
365                 weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
366                 result, std::move(ui)),
367             {content::BrowserThread::UI});
368         return;
369       } else if (system_audio_permission == SystemPermission::kRestricted ||
370                  system_audio_permission == SystemPermission::kDenied) {
371         content_settings::UpdateLocationBarUiForWebContents(web_contents);
372         final_result =
373             blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
374         system_media_permissions::SystemAudioCapturePermissionBlocked();
375       } else {
376         DCHECK_EQ(system_audio_permission, SystemPermission::kAllowed);
377         content_settings::UpdateLocationBarUiForWebContents(web_contents);
378       }
379     }
380     if (request.video_type ==
381         blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
382       const SystemPermission system_video_permission =
383           system_media_permissions::CheckSystemVideoCapturePermission();
384       UMA_HISTOGRAM_ENUMERATION(
385           "Media.Video.Capture.Mac.CameraSystemPermission.UserMedia",
386           system_video_permission);
387       if (system_video_permission == SystemPermission::kNotDetermined) {
388         // Using WeakPtr since callback can come at any time and we might be
389         // destroyed.
390         system_media_permissions::RequestSystemVideoCapturePermisson(
391             base::BindOnce(
392                 &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
393                 weak_factory_.GetWeakPtr(), web_contents, request_id, devices,
394                 result, std::move(ui)),
395             {content::BrowserThread::UI});
396         return;
397       } else if (system_video_permission == SystemPermission::kRestricted ||
398                  system_video_permission == SystemPermission::kDenied) {
399         content_settings::UpdateLocationBarUiForWebContents(web_contents);
400         final_result =
401             blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
402         system_media_permissions::SystemVideoCapturePermissionBlocked();
403       } else {
404         DCHECK_EQ(system_video_permission, SystemPermission::kAllowed);
405         content_settings::UpdateLocationBarUiForWebContents(web_contents);
406       }
407     }
408   }
409 #endif  // defined(OS_MAC)
410 
411   RepeatingMediaResponseCallback callback =
412       std::move(request_it->second.callback);
413   requests_map.erase(request_it);
414 
415   if (!requests_map.empty()) {
416     // Post a task to process next queued request. It has to be done
417     // asynchronously to make sure that calling infobar is not destroyed until
418     // after this function returns.
419     content::GetUIThreadTaskRunner({})->PostTask(
420         FROM_HERE,
421         base::BindOnce(
422             &PermissionBubbleMediaAccessHandler::ProcessQueuedAccessRequest,
423             base::Unretained(this), web_contents));
424   }
425 
426   std::move(callback).Run(devices, final_result, std::move(ui));
427 }
428 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)429 void PermissionBubbleMediaAccessHandler::Observe(
430     int type,
431     const content::NotificationSource& source,
432     const content::NotificationDetails& details) {
433   DCHECK_CURRENTLY_ON(BrowserThread::UI);
434   DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
435 
436   pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
437 }
438