// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/webid/fake_identity_request_dialog_controller.h"
#include "content/browser/webid/test/mock_mdoc_provider.h"
#include "content/browser/webid/test/webid_test_content_browser_client.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/identity_request_dialog_controller.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_federated_permission_context.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

using net::EmbeddedTestServer;
using net::HttpStatusCode;
using net::test_server::BasicHttpResponse;
using net::test_server::HttpMethod;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
using ::testing::_;
using ::testing::WithArg;

namespace content {

namespace {

constexpr char kRpHostName[] = "rp.example";

// Use localhost for IDP so that the well-known file can be fetched from the
// test server's custom port. IdpNetworkRequestManager::ComputeWellKnownUrl()
// does not enforce a specific port if the IDP is localhost.
constexpr char kIdpOrigin[] = "https://127.0.0.1";

constexpr char kExpectedConfigPath[] = "/fedcm.json";
constexpr char kExpectedWellKnownPath[] = "/.well-known/web-identity";
constexpr char kTestContentType[] = "application/json";
constexpr char kIdpForbiddenHeader[] = "Sec-FedCM-CSRF";

// TODO(crbug.com/1381501): Replace these with a standardized header once
// we collected enough metrics.
static constexpr char kGoogleSigninHeader[] = "Google-Accounts-SignIn";
static constexpr char kGoogleSignoutHeader[] = "Google-Accounts-SignOut";
static constexpr char kGoogleHeaderValue[] =
    "email=\"foo@example.com\", sessionindex=0, obfuscatedid=123";

// Token value in //content/test/data/id_assertion_endpoint.json
constexpr char kToken[] = "[not a real token]";

bool IsGetRequestWithPath(const HttpRequest& request,
                          const std::string& expected_path) {
  return request.method == HttpMethod::METHOD_GET &&
         request.relative_url == expected_path;
}

// This class implements the IdP logic, and responds to requests sent to the
// test HTTP server.
class IdpTestServer {
 public:
  struct ConfigDetails {
    HttpStatusCode status_code;
    std::string content_type;
    std::string accounts_endpoint_url;
    std::string client_metadata_endpoint_url;
    std::string id_assertion_endpoint_url;
    std::map<std::string,
             base::RepeatingCallback<std::unique_ptr<HttpResponse>(
                 const HttpRequest&)>>
        servlets;
  };

  IdpTestServer() = default;
  ~IdpTestServer() = default;

  IdpTestServer(const IdpTestServer&) = delete;
  IdpTestServer& operator=(const IdpTestServer&) = delete;

  std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
    // RP files are fetched from the /test base directory. Assume anything
    // to other paths is directed to the IdP.
    if (request.relative_url.rfind("/test", 0) == 0)
      return nullptr;

    if (request.relative_url.rfind("/header/", 0) == 0)
      return BuildIdpHeaderResponse(request);

    if (request.all_headers.find(kIdpForbiddenHeader) != std::string::npos) {
      EXPECT_EQ(request.headers.at(kIdpForbiddenHeader), "?1");
    }

    auto response = std::make_unique<BasicHttpResponse>();
    if (IsGetRequestWithPath(request, kExpectedConfigPath)) {
      BuildConfigResponseFromDetails(*response.get(), config_details_);
      return response;
    }

    if (IsGetRequestWithPath(request, kExpectedWellKnownPath)) {
      BuildWellKnownResponse(*response.get());
      return response;
    }

    if (config_details_.servlets[request.relative_url]) {
      return config_details_.servlets[request.relative_url].Run(request);
    }

    return nullptr;
  }

  std::unique_ptr<HttpResponse> BuildIdpHeaderResponse(
      const HttpRequest& request) {
    auto response = std::make_unique<BasicHttpResponse>();
    if (request.relative_url.find("/header/gsignin") != std::string::npos) {
      response->AddCustomHeader(kGoogleSigninHeader, kGoogleHeaderValue);
    } else if (request.relative_url.find("/header/gsignout") !=
               std::string::npos) {
      response->AddCustomHeader(kGoogleSignoutHeader, kGoogleHeaderValue);
    } else {
      return nullptr;
    }
    response->set_code(net::HTTP_OK);
    response->set_content_type("text/plain");
    response->set_content("Header sent.");
    return response;
  }

  void SetConfigResponseDetails(ConfigDetails details) {
    config_details_ = details;
  }

 private:
  void BuildConfigResponseFromDetails(BasicHttpResponse& response,
                                      const ConfigDetails& details) {
    std::string content = ConvertToJsonDictionary(
        {{"accounts_endpoint", details.accounts_endpoint_url},
         {"client_metadata_endpoint", details.client_metadata_endpoint_url},
         {"id_assertion_endpoint", details.id_assertion_endpoint_url}});
    response.set_code(details.status_code);
    response.set_content(content);
    response.set_content_type(details.content_type);
  }

  void BuildWellKnownResponse(BasicHttpResponse& response) {
    std::string content = base::StringPrintf("{\"provider_urls\": [\"%s\"]}",
                                             kExpectedConfigPath);
    response.set_code(net::HTTP_OK);
    response.set_content(content);
    response.set_content_type("application/json");
  }

  std::string ConvertToJsonDictionary(
      const std::map<std::string, std::string>& data) {
    std::string out = "{";
    for (auto it : data) {
      out += "\"" + it.first + "\":\"" + it.second + "\",";
    }
    if (!out.empty()) {
      out[out.length() - 1] = '}';
    }
    return out;
  }

  ConfigDetails config_details_;
};

}  // namespace

class WebIdBrowserTest : public ContentBrowserTest {
 public:
  WebIdBrowserTest() = default;
  ~WebIdBrowserTest() override = default;

  WebIdBrowserTest(const WebIdBrowserTest&) = delete;
  WebIdBrowserTest& operator=(const WebIdBrowserTest&) = delete;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");

    idp_server_ = std::make_unique<IdpTestServer>();
    https_server().SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server().ServeFilesFromSourceDirectory(GetTestDataFilePath());
    https_server().RegisterRequestHandler(base::BindRepeating(
        &IdpTestServer::HandleRequest, base::Unretained(idp_server_.get())));
    ASSERT_TRUE(https_server().Start());

    EXPECT_TRUE(NavigateToURL(
        shell(), https_server().GetURL(kRpHostName, "/title1.html")));

    test_browser_client_ = std::make_unique<WebIdTestContentBrowserClient>();
    SetTestIdentityRequestDialogController("not_real_account");
    SetTestMDocProvider();
  }

  void TearDown() override { ContentBrowserTest::TearDown(); }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    std::vector<base::test::FeatureRef> features;

    // kSplitCacheByNetworkIsolationKey feature is needed to verify
    // that the network shard for fetching the config file is different
    // from that used for other IdP transactions, to prevent data leakage.
    features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
    features.push_back(features::kFedCm);
    scoped_feature_list_.InitWithFeatures(features, {});

    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }

  net::EmbeddedTestServer& https_server() { return https_server_; }

  std::string BaseIdpUrl() {
    return std::string(kIdpOrigin) + ":" +
           base::NumberToString(https_server().port()) + "/fedcm.json";
  }

  std::string GetBasicRequestString() {
    return R"(
        (async () => {
          var x = (await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: ')" +
           BaseIdpUrl() + R"(',
                clientId: 'client_id_1',
                nonce: '12345',
              }]
            }
          }));
          return x.token;
        }) ()
    )";
  }

  IdpTestServer::ConfigDetails BuildValidConfigDetails() {
    std::string accounts_endpoint_url = "/fedcm/accounts_endpoint.json";
    std::string client_metadata_endpoint_url =
        "/fedcm/client_metadata_endpoint.json";
    std::string id_assertion_endpoint_url = "/fedcm/id_assertion_endpoint.json";
    return {net::HTTP_OK, kTestContentType, accounts_endpoint_url,
            client_metadata_endpoint_url, id_assertion_endpoint_url};
  }

  IdpTestServer* idp_server() { return idp_server_.get(); }

  void SetTestIdentityRequestDialogController(
      const std::string& dialog_selected_account) {
    auto controller = std::make_unique<FakeIdentityRequestDialogController>(
        dialog_selected_account);
    test_browser_client_->SetIdentityRequestDialogController(
        std::move(controller));
  }

  void SetTestMDocProvider() {
    auto provider = std::make_unique<MockMDocProvider>();
    test_browser_client_->SetMDocProvider(std::move(provider));
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<WebIdTestContentBrowserClient> test_browser_client_;

 private:
  EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  std::unique_ptr<IdpTestServer> idp_server_;
};

class WebIdIdpSigninStatusBrowserTest : public WebIdBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    scoped_feature_list_.InitAndEnableFeatureWithParameters(
        features::kFedCm,
        {{features::kFedCmIdpSigninStatusFieldTrialParamName, "true"}});
    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }

  ShellFederatedPermissionContext* sharing_context() {
    BrowserContext* context = shell()->web_contents()->GetBrowserContext();
    return static_cast<ShellFederatedPermissionContext*>(
        context->GetFederatedIdentityPermissionContext());
  }
};

class WebIdIdPRegistryBrowserTest : public WebIdBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    std::vector<base::test::FeatureRef> features;
    features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
    features.push_back(features::kFedCm);
    features.push_back(features::kFedCmIdPRegistration);
    scoped_feature_list_.InitWithFeatures(features, {});

    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }

  ShellFederatedPermissionContext* sharing_context() {
    BrowserContext* context = shell()->web_contents()->GetBrowserContext();
    return static_cast<ShellFederatedPermissionContext*>(
        context->GetFederatedIdentityPermissionContext());
  }
};

class WebIdAuthzBrowserTest : public WebIdBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    std::vector<base::test::FeatureRef> features;
    features.push_back(features::kFedCmAuthz);
    scoped_feature_list_.InitWithFeatures(features, {});

    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }
};

// Verify a standard login flow with IdP sign-in page.
IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, FullLoginFlow) {
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());

  EXPECT_EQ(std::string(kToken), EvalJs(shell(), GetBasicRequestString()));
}

// Verify full login flow where the IdP uses absolute rather than relative
// URLs.
IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, AbsoluteURLs) {
  IdpTestServer::ConfigDetails config_details = BuildValidConfigDetails();
  config_details.accounts_endpoint_url = "/fedcm/accounts_endpoint.json";
  config_details.client_metadata_endpoint_url =
      "/fedcm/client_metadata_endpoint.json";
  config_details.id_assertion_endpoint_url =
      "/fedcm/id_assertion_endpoint.json";

  idp_server()->SetConfigResponseDetails(config_details);

  EXPECT_EQ(std::string(kToken), EvalJs(shell(), GetBasicRequestString()));
}

// Verify an attempt to invoke FedCM with an insecure IDP path fails.
IN_PROC_BROWSER_TEST_F(WebIdBrowserTest, FailsOnHTTP) {
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());

  std::string script = R"(
        (async () => {
          var x = (await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: 'http://idp.example:)" +
                       base::NumberToString(https_server().port()) +
                       R"(/fedcm.json',
                clientId: 'client_id_1',
                nonce: '12345',
              }]
            }
          }));
          return x.token;
        }) ()
    )";

  std::string expected_error =
      "a JavaScript error: \"NetworkError: Error "
      "retrieving a token.\"\n";
  EXPECT_EQ(expected_error, EvalJs(shell(), script).error);
}

// Verify that an IdP can register itself.
IN_PROC_BROWSER_TEST_F(WebIdIdPRegistryBrowserTest, RegisterIdP) {
  GURL configURL = GURL(BaseIdpUrl());
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());

  // We navigate to the IdP's configURL so that we can run
  // the script below with the IdP's origin as the top level
  // first party context.
  EXPECT_TRUE(NavigateToURL(shell(), configURL));

  std::string script = R"(
        (async () => {
          await IdentityProvider.register(')" +
                       configURL.spec() + R"(');
          // The permission was accepted if the promise resolves.
          return true;
        }) ()
    )";

  EXPECT_EQ(true, EvalJs(shell(), script));

  EXPECT_EQ(std::vector<GURL>{configURL},
            sharing_context()->GetRegisteredIdPs());
}

// Verify that the RP cannot register the IdP across origins.
IN_PROC_BROWSER_TEST_F(WebIdIdPRegistryBrowserTest, RpCantRegisterIdP) {
  std::string script = R"(
        (async () => {
          return await IdentityProvider.register(')" +
                       BaseIdpUrl() + R"(');
        }) ()
    )";

  // TODO(crbug.com/1406698): make this error message more
  // developer friendly, since this was a call error rather
  // than a user declining the permission error.
  std::string expected_error =
      "a JavaScript error: \"NotAllowedError: "
      "User declined the permission to register the Identity Provider.\"\n";

  EXPECT_EQ(expected_error, EvalJs(shell(), script).error);
}

// Verify that an IdP can unregister itself.
IN_PROC_BROWSER_TEST_F(WebIdIdPRegistryBrowserTest, UnregisterIdP) {
  GURL configURL = GURL(BaseIdpUrl());
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());

  // We navigate to the IdP's configURL so that we can run
  // the script below with the IdP's origin as the top level
  // first party context.
  EXPECT_TRUE(NavigateToURL(shell(), configURL));

  std::string script = R"(
        (async () => {
          await IdentityProvider.register(')" +
                       configURL.spec() + R"(');
          await IdentityProvider.unregister(')" +
                       configURL.spec() + R"(');
          return true;
        }) ()
    )";

  EXPECT_EQ(true, EvalJs(shell(), script));

  EXPECT_TRUE(sharing_context()->GetRegisteredIdPs().empty());
}

// Verify that IDP sign-in headers work.
IN_PROC_BROWSER_TEST_F(WebIdIdpSigninStatusBrowserTest, IdpSigninToplevel) {
  GURL url = https_server().GetURL(kRpHostName, "/header/gsignin");
  EXPECT_FALSE(sharing_context()
                   ->GetIdpSigninStatus(url::Origin::Create(url))
                   .has_value());
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url));
  auto value = sharing_context()->GetIdpSigninStatus(url::Origin::Create(url));
  ASSERT_TRUE(value.has_value());
  EXPECT_TRUE(*value);
}

// Verify that IDP sign-out headers work.
IN_PROC_BROWSER_TEST_F(WebIdIdpSigninStatusBrowserTest, IdpSignoutToplevel) {
  GURL url = https_server().GetURL(kRpHostName, "/header/gsignout");
  EXPECT_FALSE(sharing_context()
                   ->GetIdpSigninStatus(url::Origin::Create(url))
                   .has_value());
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url));
  auto value = sharing_context()->GetIdpSigninStatus(url::Origin::Create(url));
  ASSERT_TRUE(value.has_value());
  EXPECT_FALSE(*value);
}

// Verify that IDP sign-in/out headers work in subresources.
IN_PROC_BROWSER_TEST_F(WebIdIdpSigninStatusBrowserTest,
                       IdpSigninAndOutSubresource) {
  static constexpr char script[] = R"(
    (async () => {
      var resp = await fetch('/header/gsign%s');
      return resp.status;
    }) ();
  )";

  GURL url_for_origin = https_server().GetURL(kRpHostName, "/header/");
  url::Origin origin = url::Origin::Create(url_for_origin);
  EXPECT_FALSE(sharing_context()->GetIdpSigninStatus(origin).has_value());
  {
    base::RunLoop run_loop;
    sharing_context()->SetIdpStatusClosureForTesting(run_loop.QuitClosure());
    EXPECT_EQ(200, EvalJs(shell(), base::StringPrintf(script, "in")));
    run_loop.Run();
  }
  auto value = sharing_context()->GetIdpSigninStatus(origin);
  ASSERT_TRUE(value.has_value());
  EXPECT_TRUE(*value);

  {
    base::RunLoop run_loop;
    sharing_context()->SetIdpStatusClosureForTesting(run_loop.QuitClosure());
    EXPECT_EQ(200, EvalJs(shell(), base::StringPrintf(script, "out")));
    run_loop.Run();
  }
  value = sharing_context()->GetIdpSigninStatus(origin);
  ASSERT_TRUE(value.has_value());
  EXPECT_FALSE(*value);
}

class WebIdMDocsBrowserTest : public WebIdBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    std::vector<base::test::FeatureRef> features;
    features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
    features.push_back(features::kWebIdentityMDocs);
    scoped_feature_list_.InitWithFeatures(features, {});

    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }

  ShellFederatedPermissionContext* sharing_context() {
    BrowserContext* context = shell()->web_contents()->GetBrowserContext();
    return static_cast<ShellFederatedPermissionContext*>(
        context->GetFederatedIdentityPermissionContext());
  }
};

// Test that an mdoc can be requested via a JS API.
IN_PROC_BROWSER_TEST_F(WebIdMDocsBrowserTest, RequestMDoc) {
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());
  MockMDocProvider* mdoc_provider = static_cast<MockMDocProvider*>(
      test_browser_client_->GetMDocProviderForTests());

  EXPECT_CALL(*mdoc_provider, RequestMDoc(_, _, _, _, _))
      .WillOnce(WithArg<4>([](MDocProvider::MDocCallback callback) {
        std::move(callback).Run("test-mdoc");
      }));

  std::string script = R"(
        (async () => {
          const {token} = await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: '',
                clientId: '',
                mdoc: {
                  documentType: 'test_document_type',
                  readerPublicKey: 'test_reader_public_key',
                  requestedElements: [
                    {
                      namespace: 'test_namespace',
                      name: 'test_name'
                    }
                  ],
                },
              }],
            },
          });
          return token;
        }) ()
    )";

  EXPECT_EQ("test-mdoc", EvalJs(shell(), script));
}

// Test that when there's a pending mdoc request, a second `get` call should be
// rejected.
IN_PROC_BROWSER_TEST_F(WebIdMDocsBrowserTest,
                       OnlyOneInFlightMDocRequestIsAllowed) {
  idp_server()->SetConfigResponseDetails(BuildValidConfigDetails());
  MockMDocProvider* mdoc_provider = static_cast<MockMDocProvider*>(
      test_browser_client_->GetMDocProviderForTests());

  std::string script = R"(
        (async () => {
          const {token} = await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: '',
                clientId: '',
                mdoc: {
                  documentType: 'test_document_type',
                  readerPublicKey: 'test_reader_public_key',
                  requestedElements: [
                    {
                      namespace: 'test_namespace',
                      name: 'test_name'
                    }
                  ],
                },
              }],
            },
          });
          return token;
        }) ()
    )";

  EXPECT_CALL(*mdoc_provider, RequestMDoc(_, _, _, _, _))
      .WillOnce(WithArg<4>([&](MDocProvider::MDocCallback callback) {
        EXPECT_EQ(
            "a JavaScript error: \"AbortError: Only one "
            "navigator.credentials.get request may be outstanding at one "
            "time.\"\n",
            EvalJs(shell(), script).error);
        std::move(callback).Run("test-mdoc");
      }));

  EXPECT_EQ("test-mdoc", EvalJs(shell(), script));
}

// Verify that the Authz parameters are passed to the id assertion endpoint.
IN_PROC_BROWSER_TEST_F(WebIdAuthzBrowserTest, Authz_noPopUpWindow) {
  IdpTestServer::ConfigDetails config_details = BuildValidConfigDetails();

  // Points the id assertion endpoint to a servlet.
  config_details.id_assertion_endpoint_url = "/authz/id_assertion_endpoint.php";

  // Add a servlet to serve a response for the id assertoin endpoint.
  config_details.servlets["/authz/id_assertion_endpoint.php"] =
      base::BindRepeating(
          [](const HttpRequest& request) -> std::unique_ptr<HttpResponse> {
            EXPECT_EQ(request.method, HttpMethod::METHOD_POST);
            EXPECT_EQ(request.has_content, true);

            std::string content;
            content += "client_id=client_id_1&";
            content += "nonce=12345&";
            content += "account_id=not_real_account&";
            content += "disclosure_text_shown=false&";
            // Asserts that the scope, response_type and params parameters
            // were passed correctly to the id assertion endpoint.
            content += "scope=name+email+picture&";
            content += "response_type=id_token+code&";
            content += "%3F+gets+://=%26+escaped+!&";
            content += "foo=bar&";
            content += "hello=world";

            EXPECT_EQ(request.content, content);

            auto response = std::make_unique<BasicHttpResponse>();
            response->set_code(net::HTTP_OK);
            response->set_content_type("text/json");
            // Standard scopes were used, so no extra permission needed.
            // Return a token immediately.
            response->set_content(R"({"token": "[request lgtm!]"})");
            return response;
          });

  idp_server()->SetConfigResponseDetails(config_details);

  std::string script = R"(
        (async () => {
          var x = (await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: ')" +
                       BaseIdpUrl() + R"(',
                clientId: 'client_id_1',
                nonce: '12345',
                scope: [
                  'name',
                  'email',
                  'picture',
                ],
                responseType: [
                  'id_token',
                  'code'
                ],
                params: {
                  'foo': 'bar',
                  'hello': 'world',
                  '? gets ://': '& escaped !',
                }
              }]
            }
          }));
          return x.token;
        }) ()
    )";

  EXPECT_EQ(std::string("[request lgtm!]"), EvalJs(shell(), script));
}

// Verify that the id assertion endpoint can request a pop-up window.
IN_PROC_BROWSER_TEST_F(WebIdAuthzBrowserTest, Authz_openPopUpWindow) {
  IdpTestServer::ConfigDetails config_details = BuildValidConfigDetails();

  // Points the id assertion endpoint to a servlet.
  config_details.id_assertion_endpoint_url = "/authz/id_assertion_endpoint.php";

  // Add a servlet to serve a response for the id assertoin endpoint.
  config_details.servlets["/authz/id_assertion_endpoint.php"] =
      base::BindRepeating(
          [](const HttpRequest& request) -> std::unique_ptr<HttpResponse> {
            std::string content;
            content += "client_id=client_id_1&";
            content += "nonce=12345&";
            content += "account_id=not_real_account&";
            content += "disclosure_text_shown=false&";
            content += "scope=calendar.readonly";

            EXPECT_EQ(request.content, content);

            auto response = std::make_unique<BasicHttpResponse>();
            response->set_code(net::HTTP_OK);
            response->set_content_type("text/json");
            // scope=calendar.readonly was requested, so need to
            // return a continuation url instead of a token.
            response->set_content(
                R"({"continue_on": "https://idp.example/continue.php"})");
            return response;
          });

  idp_server()->SetConfigResponseDetails(config_details);

  std::string script = R"(
        (async () => {
          var x = (await navigator.credentials.get({
            identity: {
              providers: [{
                configURL: ')" +
                       BaseIdpUrl() + R"(',
                clientId: 'client_id_1',
                nonce: '12345',
                scope: [
                  'calendar.readonly'
                ],
              }]
            }
          }));
          return x.token;
        }) ()
    )";

  // Assert that a fake token was returned from the pop-up window,
  // as opposed to directly from the id assertion endpoint.
  EXPECT_EQ(std::string("--fake-token-from-pop-up-window--"),
            EvalJs(shell(), script));
}

}  // namespace content