From 69451423518fb3cd304fec79816fcc12173c54a3 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Thu, 5 Sep 2019 20:09:28 +0000 Subject: [PATCH] Bug 1548087 - enumerateDevices should only expose devices which are allowed to use by FeturePolicy r=baku,jib Differential Revision: https://phabricator.services.mozilla.com/D42958 --HG-- extra : moz-landing-system : lando --- dom/media/MediaManager.cpp | 65 +++++++++++++------ .../mochitest/test_enumerateDevices.html | 4 +- ...erateDevices-not-allowed-camera.https.html | 30 +++++++++ ...ices-not-allowed-camera.https.html.headers | 1 + ...numerateDevices-not-allowed-mic.https.html | 30 +++++++++ ...Devices-not-allowed-mic.https.html.headers | 1 + .../MediaDevices-enumerateDevices.https.html | 30 ++++----- 7 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html create mode 100644 testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html.headers create mode 100644 testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html create mode 100644 testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html.headers diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 226d776e79f8..8c54ab8100e6 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -3161,8 +3161,10 @@ RefPtr MediaManager::EnumerateDevices( __func__); } uint64_t windowId = aWindow->WindowID(); + Document* doc = aWindow->GetExtantDoc(); + MOZ_ASSERT(doc); - nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal(); + nsIPrincipal* principal = doc->NodePrincipal(); RefPtr windowListener = GetWindowListener(windowId); @@ -3183,8 +3185,30 @@ RefPtr MediaManager::EnumerateDevices( DeviceEnumerationType videoEnumerationType = DeviceEnumerationType::Normal; DeviceEnumerationType audioEnumerationType = DeviceEnumerationType::Normal; - bool resistFingerprinting = nsContentUtils::ResistFingerprinting(aCallerType); + // Only expose devices which are allowed to use: + // https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices + MediaSourceEnum videoType = dom::FeaturePolicyUtils::IsFeatureAllowed( + doc, NS_LITERAL_STRING("camera")) + ? MediaSourceEnum::Camera + : MediaSourceEnum::Other; + MediaSourceEnum audioType = dom::FeaturePolicyUtils::IsFeatureAllowed( + doc, NS_LITERAL_STRING("microphone")) + ? MediaSourceEnum::Microphone + : MediaSourceEnum::Other; + + auto devices = MakeRefPtr(); + MediaSinkEnum audioOutputType = MediaSinkEnum::Other; + // TODO bug Bug 1577199 we don't seem to support the "speaker" feature policy + // yet. + if (Preferences::GetBool("media.setsinkid.enabled")) { + audioOutputType = MediaSinkEnum::Speaker; + } else if (audioType == MediaSourceEnum::Other && + videoType == MediaSourceEnum::Other) { + return DevicesPromise::CreateAndResolve(devices, __func__); + } + + bool resistFingerprinting = nsContentUtils::ResistFingerprinting(aCallerType); // In order of precedence: resist fingerprinting > loopback > fake pref if (resistFingerprinting) { videoEnumerationType = DeviceEnumerationType::Fake; @@ -3194,30 +3218,29 @@ RefPtr MediaManager::EnumerateDevices( nsAutoCString videoLoopDev, audioLoopDev; bool wantFakes = Preferences::GetBool("media.navigator.streams.fake"); // Video - Preferences::GetCString("media.video_loopback_dev", videoLoopDev); - // Loopback prefs take precedence over fake prefs - if (!videoLoopDev.IsEmpty()) { - videoEnumerationType = DeviceEnumerationType::Loopback; - } else if (wantFakes) { - videoEnumerationType = DeviceEnumerationType::Fake; + if (videoType == MediaSourceEnum::Camera) { + Preferences::GetCString("media.video_loopback_dev", videoLoopDev); + // Loopback prefs take precedence over fake prefs + if (!videoLoopDev.IsEmpty()) { + videoEnumerationType = DeviceEnumerationType::Loopback; + } else if (wantFakes) { + videoEnumerationType = DeviceEnumerationType::Fake; + } } + // Audio - Preferences::GetCString("media.audio_loopback_dev", audioLoopDev); - // Loopback prefs take precedence over fake prefs - if (!audioLoopDev.IsEmpty()) { - audioEnumerationType = DeviceEnumerationType::Loopback; - } else if (wantFakes) { - audioEnumerationType = DeviceEnumerationType::Fake; + if (audioType == MediaSourceEnum::Microphone) { + Preferences::GetCString("media.audio_loopback_dev", audioLoopDev); + // Loopback prefs take precedence over fake prefs + if (!audioLoopDev.IsEmpty()) { + audioEnumerationType = DeviceEnumerationType::Loopback; + } else if (wantFakes) { + audioEnumerationType = DeviceEnumerationType::Fake; + } } } - MediaSinkEnum audioOutputType = MediaSinkEnum::Other; - if (Preferences::GetBool("media.setsinkid.enabled")) { - audioOutputType = MediaSinkEnum::Speaker; - } - auto devices = MakeRefPtr(); - return EnumerateDevicesImpl(windowId, MediaSourceEnum::Camera, - MediaSourceEnum::Microphone, audioOutputType, + return EnumerateDevicesImpl(windowId, videoType, audioType, audioOutputType, videoEnumerationType, audioEnumerationType, false, devices) ->Then( diff --git a/dom/media/tests/mochitest/test_enumerateDevices.html b/dom/media/tests/mochitest/test_enumerateDevices.html index 9fc94d1aa3d5..b8d70ebbe102 100644 --- a/dom/media/tests/mochitest/test_enumerateDevices.html +++ b/dom/media/tests/mochitest/test_enumerateDevices.html @@ -106,7 +106,9 @@ runTest(async () => { let [sameOriginDevices, differentOriginDevices] = origins.map(o => devicesMap.get(o)); is(sameOriginDevices.length, devices.length); - is(differentOriginDevices.length, devices.length); + // Camera and microphone are allowed for documents that are same-origin by + // default. + is(differentOriginDevices.length, 0); [...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d)); for (let device of sameOriginDevices) { diff --git a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html new file mode 100644 index 000000000000..1bb086dadddd --- /dev/null +++ b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html @@ -0,0 +1,30 @@ + + + +enumerateDevices: test enumerateDevices should not expose camera devices if they are not allowed to use + + + + +

Description

+

This test checks for the presence of camera in +navigator.mediaDevices.enumerateDevices() method.

+
+ + + + + diff --git a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html.headers b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html.headers new file mode 100644 index 000000000000..2adc5e237fce --- /dev/null +++ b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-camera.https.html.headers @@ -0,0 +1 @@ +Feature-Policy: camera 'none' diff --git a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html new file mode 100644 index 000000000000..8d535ce551de --- /dev/null +++ b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html @@ -0,0 +1,30 @@ + + + +enumerateDevices: test enumerateDevices should not expose microphone devices if they are not allowed to use + + + + +

Description

+

This test checks for the presence of microphone in +navigator.mediaDevices.enumerateDevices() method.

+
+ + + + + diff --git a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html.headers b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html.headers new file mode 100644 index 000000000000..a86e0a077851 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices-not-allowed-mic.https.html.headers @@ -0,0 +1 @@ +Feature-Policy: microphone 'none' diff --git a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html index 76ca435cf588..efb460731690 100644 --- a/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html +++ b/testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html @@ -17,26 +17,26 @@ "use strict"; //NOTE ALEX: for completion, a test for ondevicechange event is missing. promise_test(async () => { - assert_true(undefined !== navigator.mediaDevices.enumerateDevices, "navigator.mediaDevices.enumerateDevices exists"); - const device_list = await navigator.mediaDevices.enumerateDevices(); - for (const mediainfo of device_list) { - assert_true(undefined !== mediainfo.deviceId, "mediaInfo's deviceId should exist."); - assert_true(undefined !== mediainfo.kind, "mediaInfo's kind should exist."); - assert_in_array(mediainfo.kind, ["videoinput", "audioinput", "audiooutput"]); - assert_true(undefined !== mediainfo.label, "mediaInfo's label should exist."); - assert_true(undefined !== mediainfo.groupId, "mediaInfo's groupId should exist."); + assert_not_equals(navigator.mediaDevices.enumerateDevices, undefined, "navigator.mediaDevices.enumerateDevices exists"); + const deviceList = await navigator.mediaDevices.enumerateDevices(); + for (const mediaInfo of deviceList) { + assert_not_equals(mediaInfo.deviceId, undefined, "mediaInfo's deviceId should exist."); + assert_not_equals(mediaInfo.kind, undefined, "mediaInfo's kind should exist."); + assert_not_equals(mediaInfo.label, undefined, "mediaInfo's label should exist."); + assert_not_equals(mediaInfo.groupId, undefined, "mediaInfo's groupId should exist."); + assert_in_array(mediaInfo.kind, ["videoinput", "audioinput", "audiooutput"]); } }, "mediaDevices.enumerateDevices() is present and working"); promise_test(async () => { - const device_list = await navigator.mediaDevices.enumerateDevices(); - for (const mediainfo of device_list) { - if (mediainfo.kind == "audioinput" || mediainfo.kind == "videoinput") { - assert_true(mediainfo instanceof InputDeviceInfo); - } else if ( mediainfo.kind == "audiooutput" ) { - assert_true(mediainfo instanceof MediaDeviceInfo); + const deviceList = await navigator.mediaDevices.enumerateDevices(); + for (const mediaInfo of deviceList) { + if (mediaInfo.kind == "audioinput" || mediaInfo.kind == "videoinput") { + assert_true(mediaInfo instanceof InputDeviceInfo); + } else if ( mediaInfo.kind == "audiooutput" ) { + assert_true(mediaInfo instanceof MediaDeviceInfo); } else { - assert_unreached("mediainfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.") + assert_unreached("mediaInfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.") } } }, "InputDeviceInfo is supported");