Commit 795efb16 authored by Maksim Ivanov's avatar Maksim Ivanov Committed by Commit Bot

Reland-2 Add browser tests for smart card SAML login

This relands the test from the commit
8fd76b23 that
got reverted for the second time due to flakiness. The test is relanded
as DISABLED, in order to land all the lgtm'ed plumbing that would anyway
be necessary even after fixing all possible flakiness.

Original change's description:
> Add browser tests for smart card SAML login
>
> Provide test coverage for the scenario where a new user authenticates
> via SAML using a smart card (or, generally speaking, using a security
> token via an extension using the chrome.certificateProvider API).
>
> This also provides some test coverage for the <security-token-pin>
> Polymer element used on the Login Screen.
>
> Bug: 1033936
> Change-Id: I5ded32e0570eb7227c77b954c33d12c1a8a62914
> Reviewed-on:
https://chromium-review.googlesource.com/c/chromium/src/+/1968989
> Reviewed-by: Alexander Alekseev <alemate@chromium.org>
> Reviewed-by: Denis Kuznetsov [CET] <antrim@chromium.org>
> Reviewed-by: Alexander Hendrich <hendrich@chromium.org>
> Commit-Queue: Maksim Ivanov <emaxx@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#734510}

Bug: 1033936
Tbr: alemate@chromium.org, hendrich@chromium.org
Test: run the test locally with is_chrome_branded=true
Change-Id: Ie3431acfdb589bd7d9ed443d283de996af18a266
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2032116
Commit-Queue: Maksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738234}
parent 2855616d
......@@ -1634,6 +1634,8 @@ source_set("chromeos") {
"login/users/supervised_user_manager.h",
"login/users/supervised_user_manager_impl.cc",
"login/users/supervised_user_manager_impl.h",
"login/users/test_users.cc",
"login/users/test_users.h",
"login/users/user_manager_interface.h",
"login/version_info_updater.cc",
"login/version_info_updater.h",
......
......@@ -14,9 +14,7 @@
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "chrome/common/extensions/api/certificate_provider.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/notification_details.h"
......@@ -103,6 +101,11 @@ bool RsaSignPrehashed(const EVP_PKEY& key,
return true;
}
void SendReplyToJs(extensions::TestSendMessageFunction* function,
const base::Value& response) {
function->Reply(ConvertValueToJson(response));
}
} // namespace
TestCertificateProviderExtension::TestCertificateProviderExtension(
......@@ -140,39 +143,50 @@ void TestCertificateProviderExtension::Observe(
return;
}
const std::string& message =
content::Details<std::pair<std::string, bool*>>(details)->first;
const auto typed_details =
content::Details<std::pair<std::string, bool*>>(details);
const std::string& message = typed_details->first;
bool* const listener_will_respond = typed_details->second;
// Handle the request and reply to it (possibly, asynchronously).
base::Value message_value = ParseJsonToValue(message);
CHECK(message_value.is_list());
CHECK(message_value.GetList().size());
CHECK(message_value.GetList()[0].is_string());
const std::string& request_type = message_value.GetList()[0].GetString();
base::Value response;
ReplyToJsCallback send_reply_to_js_callback =
base::BindOnce(&SendReplyToJs, base::Unretained(function));
*listener_will_respond = true;
if (request_type == "onCertificatesRequested") {
CHECK_EQ(message_value.GetList().size(), 1U);
response = HandleCertificatesRequest();
} else if (request_type == "onSignDigestRequested") {
CHECK_EQ(message_value.GetList().size(), 2U);
response =
HandleSignDigestRequest(/*sign_request=*/message_value.GetList()[1]);
HandleCertificatesRequest(std::move(send_reply_to_js_callback));
} else if (request_type == "onSignatureRequested") {
CHECK_EQ(message_value.GetList().size(), 3U);
HandleSignatureRequest(
/*sign_request=*/message_value.GetList()[1],
/*pin_user_input=*/message_value.GetList()[2],
std::move(send_reply_to_js_callback));
} else {
LOG(FATAL) << "Unexpected JS message type: " << request_type;
}
function->Reply(ConvertValueToJson(response));
}
base::Value TestCertificateProviderExtension::HandleCertificatesRequest() {
void TestCertificateProviderExtension::HandleCertificatesRequest(
ReplyToJsCallback callback) {
base::Value cert_info_values(base::Value::Type::LIST);
if (!should_fail_certificate_requests_)
cert_info_values.Append(MakeCertInfoValue(*certificate_));
return cert_info_values;
std::move(callback).Run(cert_info_values);
}
base::Value TestCertificateProviderExtension::HandleSignDigestRequest(
const base::Value& sign_request) {
void TestCertificateProviderExtension::HandleSignatureRequest(
const base::Value& sign_request,
const base::Value& pin_user_input,
ReplyToJsCallback callback) {
CHECK_EQ(*sign_request.FindKey("certificate"),
ConvertBytesToValue(GetCertDer(*certificate_)));
const int sign_request_id = sign_request.FindKey("signRequestId")->GetInt();
const std::vector<uint8_t> digest =
ExtractBytesFromValue(*sign_request.FindKey("digest"));
......@@ -187,10 +201,42 @@ base::Value TestCertificateProviderExtension::HandleSignDigestRequest(
else
LOG(FATAL) << "Unexpected signature request hash: " << hash;
if (should_fail_sign_digest_requests_)
return base::Value();
if (should_fail_sign_digest_requests_) {
// Simulate a failure.
std::move(callback).Run(/*response=*/base::Value());
return;
}
base::Value response(base::Value::Type::DICTIONARY);
if (required_pin_.has_value()) {
if (pin_user_input.is_none()) {
// The PIN is required but not specified yet, so request it via the JS
// side before generating the signature.
base::Value pin_request_parameters(base::Value::Type::DICTIONARY);
pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
response.SetKey("requestPin", std::move(pin_request_parameters));
std::move(callback).Run(response);
return;
}
if (pin_user_input.GetString() != *required_pin_) {
// The PIN is wrong, so retry the PIN request with displaying an error.
base::Value pin_request_parameters(base::Value::Type::DICTIONARY);
pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
pin_request_parameters.SetStringKey("errorType", "INVALID_PIN");
response.SetKey("requestPin", std::move(pin_request_parameters));
std::move(callback).Run(response);
return;
}
// The entered PIN is correct. Stop the PIN request and proceed to
// generating the signature.
base::Value stop_pin_request_parameters(base::Value::Type::DICTIONARY);
stop_pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
response.SetKey("stopPinRequest", std::move(stop_pin_request_parameters));
}
// Generate and return a valid signature.
std::vector<uint8_t> signature;
CHECK(
RsaSignPrehashed(*private_key_, openssl_digest_type, digest, &signature));
return ConvertBytesToValue(signature);
response.SetKey("signature", ConvertBytesToValue(signature));
std::move(callback).Run(response);
}
......@@ -8,8 +8,11 @@
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/values.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "third_party/boringssl/src/include/openssl/base.h"
......@@ -47,6 +50,10 @@ class TestCertificateProviderExtension final
return certificate_;
}
// Sets the PIN that will be required when doing every signature request.
// (By default, no PIN is requested.)
void set_require_pin(const std::string& pin) { required_pin_ = pin; }
// Sets whether the extension should respond with a failure to the
// onCertificatesRequested requests.
void set_should_fail_certificate_requests(
......@@ -62,18 +69,26 @@ class TestCertificateProviderExtension final
}
private:
using ReplyToJsCallback =
base::OnceCallback<void(const base::Value& response)>;
// content::NotificationObserver implementation:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override;
base::Value HandleCertificatesRequest();
base::Value HandleSignDigestRequest(const base::Value& sign_request);
void HandleCertificatesRequest(ReplyToJsCallback callback);
void HandleSignatureRequest(const base::Value& sign_request,
const base::Value& pin_user_input,
ReplyToJsCallback callback);
content::BrowserContext* const browser_context_;
const std::string extension_id_;
const scoped_refptr<net::X509Certificate> certificate_;
const bssl::UniquePtr<EVP_PKEY> private_key_;
// When non-empty, contains the expected PIN; the implementation will request
// the PIN on every signature request in this case.
base::Optional<std::string> required_pin_;
bool should_fail_certificate_requests_ = false;
bool should_fail_sign_digest_requests_ = false;
content::NotificationRegistrar notification_registrar_;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <iterator>
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/values.h"
#include "chrome/browser/chromeos/certificate_provider/test_certificate_provider_extension.h"
#include "chrome/browser/chromeos/certificate_provider/test_certificate_provider_extension_login_screen_mixin.h"
#include "chrome/browser/chromeos/login/saml/test_client_cert_saml_idp_mixin.h"
#include "chrome/browser/chromeos/login/test/device_state_mixin.h"
#include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
#include "chrome/browser/chromeos/login/test/https_forwarder.h"
#include "chrome/browser/chromeos/login/test/js_checker.h"
#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
#include "chrome/browser/chromeos/login/test/scoped_policy_update.h"
#include "chrome/browser/chromeos/login/test/session_manager_state_waiter.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/users/test_users.h"
#include "chrome/browser/chromeos/scoped_test_system_nss_key_slot_mixin.h"
#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "google_apis/gaia/fake_gaia.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
// Pattern for the DeviceLoginScreenAutoSelectCertificateForUrls admin policy
// that automatically selects the certificate exposed by the test certificate
// provider extension.
constexpr char kClientCertAutoSelectPolicyValue[] =
R"({"pattern": "*", "filter": {"ISSUER": {"CN": "B CA"}}})";
// The DER-encoded "CN=B CA" DistinguishedName value, which represents the
// issuer of the certificate exposed by the test certificate provider extension.
constexpr uint8_t kClientCertCaName[] = {0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b,
0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
0x04, 0x42, 0x20, 0x43, 0x41};
// The PIN code that the test certificate provider extension is configured to
// expect.
constexpr char kCorrectPin[] = "17093";
std::string GetClientCertCaName() {
return std::string(std::begin(kClientCertCaName),
std::end(kClientCertCaName));
}
std::string GetActiveUserEmail() {
const user_manager::User* user =
user_manager::UserManager::Get()->GetActiveUser();
if (!user)
return std::string();
return user->GetAccountId().GetUserEmail();
}
} // namespace
// Tests the challenge-response type of SAML login (e.g., the smart card based
// user login).
//
// The rough sequence of steps in the correct scenario:
// 1. the user e-mail is entered into the Gaia form;
// 2. the browser is redirected to the (first) SAML page;
// 3. the browser is redirected to the second SAML page, which requests a client
// certificate during the TLS handshake;
// 4. the test certificate provider extension returns the client certificate;
// 5. the TLS handshake with the SAML server continues, and the server makes a
// challenge request;
// 6. the test certificate provider extension receives the challenge and
// requests the PIN, for which the PIN dialog is shown on top of the SAML
// page frame;
// 7. the PIN is entered;
// 8. the test certificate provider extension receives the PIN and generates the
// signature of the challenge, which is forwarded to the SAML server;
// 9. the TLS handshake with the SAML server completes, and the SAML page
// redirects back to Gaia to signal about the successful authentication;
// 10. the user session starts.
class SecurityTokenSamlTest : public OobeBaseTest {
protected:
SecurityTokenSamlTest() {
// Allow the forced installation of extensions in the background.
needs_background_networking_ = true;
SetClientCertAutoSelectPolicy();
ConfigureFakeGaia();
}
SecurityTokenSamlTest(const SecurityTokenSamlTest&) = delete;
SecurityTokenSamlTest& operator=(const SecurityTokenSamlTest&) = delete;
void SetUpCommandLine(base::CommandLine* command_line) override {
OobeBaseTest::SetUpCommandLine(command_line);
// Skip OOBE post-login screens (like the sync consent screen in branded
// builders) to make the test simpler by directly proceeding to user session
// after the sign-in.
command_line->AppendSwitch(switches::kOobeSkipPostLogin);
// Avoid aborting the user sign-in due to the user policy requests not being
// faked in the test.
command_line->AppendSwitch(
chromeos::switches::kAllowFailedPolicyFetchForTest);
}
void SetUpOnMainThread() override {
OobeBaseTest::SetUpOnMainThread();
cert_provider_extension_mixin_.test_certificate_provider_extension()
->set_require_pin(kCorrectPin);
gaia_mixin_.fake_gaia()->RegisterSamlUser(
saml_test_users::kFirstUserCorpExampleComEmail,
saml_idp_mixin_.GetSamlPageUrl());
StartObservingLoginUiMessages();
}
int pin_dialog_shown_count() const { return pin_dialog_shown_count_; }
void StartSignIn() {
LoginDisplayHost::default_host()
->GetOobeUI()
->GetView<GaiaScreenHandler>()
->ShowSigninScreenForTest(
saml_test_users::kFirstUserCorpExampleComEmail,
/*password=*/std::string(),
/*services=*/"[]");
}
// Waits until the security token PIN dialog appears on the login screen.
void WaitForPinDialog() {
if (pin_dialog_shown_count_)
return;
base::RunLoop run_loop;
pin_dialog_shown_run_loop_ = &run_loop;
// Quit() will be called from OnPinDialogShownMessage().
run_loop.Run();
pin_dialog_shown_run_loop_ = nullptr;
}
// Enters the security token PIN by simulating click events on the on-screen
// keypad.
void InputPinByClickingKeypad(const std::string& pin) {
for (char pin_character : pin) {
const std::string digit_button_id =
std::string("digitButton") + pin_character;
test::OobeJS().ClickOnPath(
{"gaia-signin", "pinDialog", "pinKeyboard", digit_button_id});
}
}
private:
// Sets up the client certificate to be automatically selected for the SAML
// page (by default a certificate selector needs to be shown).
void SetClientCertAutoSelectPolicy() {
device_state_mixin_.RequestDevicePolicyUpdate()
->policy_payload()
->mutable_device_login_screen_auto_select_certificate_for_urls()
->add_login_screen_auto_select_certificate_rules(
kClientCertAutoSelectPolicyValue);
}
void ConfigureFakeGaia() {
// FakeGaia uses the fake merge session parameters for preparing the result
// of the SAML sign-in.
gaia_mixin_.set_initialize_fake_merge_session(false);
gaia_mixin_.fake_gaia()->SetFakeMergeSessionParams(
saml_test_users::kFirstUserCorpExampleComEmail,
/*auth_sid_cookie=*/std::string(),
/*auth_lsid_cookie=*/std::string());
}
// Subscribes for the notifications from the Login Screen UI,
void StartObservingLoginUiMessages() {
GetLoginUI()->RegisterMessageCallback(
"securityTokenPinDialogShownForTest",
base::BindRepeating(&SecurityTokenSamlTest::OnPinDialogShownMessage,
weak_factory_.GetWeakPtr()));
}
// Called when the Login Screen UI notifies that the PIN dialog is shown.
void OnPinDialogShownMessage(const base::ListValue*) {
++pin_dialog_shown_count_;
if (pin_dialog_shown_run_loop_)
pin_dialog_shown_run_loop_->Quit();
}
ScopedTestSystemNSSKeySlotMixin system_nss_key_slot_mixin_{&mixin_host_};
FakeGaiaMixin gaia_mixin_{&mixin_host_, embedded_test_server()};
DeviceStateMixin device_state_mixin_{
&mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
TestClientCertSamlIdpMixin saml_idp_mixin_{
&mixin_host_, &gaia_mixin_,
/*client_cert_authorities=*/{GetClientCertCaName()}};
TestCertificateProviderExtensionLoginScreenMixin
cert_provider_extension_mixin_{&mixin_host_, &device_state_mixin_};
int pin_dialog_shown_count_ = 0;
base::RunLoop* pin_dialog_shown_run_loop_ = nullptr;
base::WeakPtrFactory<SecurityTokenSamlTest> weak_factory_{this};
};
#define NEVER_ENABLED_Basic DISABLED_Basic
// Tests the successful login scenario with the correct PIN.
// TODO(crbug.com/1033936): Fix the flakiness and enable it.
IN_PROC_BROWSER_TEST_F(SecurityTokenSamlTest, NEVER_ENABLED_Basic) {
WaitForSigninScreen();
test::OobeJS().ExpectHiddenPath({"gaia-signin", "pinDialog"});
StartSignIn();
WaitForPinDialog();
test::OobeJS().ExpectVisiblePath({"gaia-signin", "pinDialog"});
InputPinByClickingKeypad(kCorrectPin);
test::OobeJS().ClickOnPath({"gaia-signin", "pinDialog", "submit"});
test::WaitForPrimaryUserSessionStart();
EXPECT_EQ(saml_test_users::kFirstUserCorpExampleComEmail,
GetActiveUserEmail());
EXPECT_EQ(1, pin_dialog_shown_count());
}
} // namespace chromeos
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/saml/test_client_cert_saml_idp_mixin.h"
#include "base/bind.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
#include "chrome/browser/chromeos/login/test/https_forwarder.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
// Name of the "RelayState" URL parameter from the SAML specification.
constexpr char kSamlRelayStateUrlParam[] = "RelayState";
// URL path of the first SAML page. The FakeGaia will redirect the browser to
// this page when the sign-in for |kUserEmail| is started. This page will
// redirect to the second SAML page (see below).
constexpr char kSamlPageUrlPath[] = "saml-page";
// URL path of the second SAML page. This page is configured to authenticate the
// client via a client certificate.
constexpr char kSamlWithClientCertsPageUrlPath[] = "saml-client-cert-page";
// The response passed by the second SAML page to Gaia after successful
// authentication.
constexpr char kSamlResponse[] = "saml-response";
} // namespace
TestClientCertSamlIdpMixin::TestClientCertSamlIdpMixin(
InProcessBrowserTestMixinHost* host,
FakeGaiaMixin* gaia_mixin,
const std::vector<std::string>& client_cert_authorities)
: InProcessBrowserTestMixin(host), gaia_mixin_(gaia_mixin) {
saml_server_.RegisterRequestHandler(
base::BindRepeating(&TestClientCertSamlIdpMixin::HandleSamlServerRequest,
base::Unretained(this)));
// Set up |saml_with_client_certs_server_| to request a client certificate.
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
// TODO(crbug.com/792204): Enable TLS 1.3 after the
// chrome.certificateProvider API supports it.
ssl_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1_2;
ssl_config.cert_authorities = client_cert_authorities;
saml_with_client_certs_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK,
ssl_config);
saml_with_client_certs_server_.RegisterRequestHandler(base::BindRepeating(
&TestClientCertSamlIdpMixin::HandleSamlWithClientCertsServerRequest,
base::Unretained(this)));
}
TestClientCertSamlIdpMixin::~TestClientCertSamlIdpMixin() = default;
GURL TestClientCertSamlIdpMixin::GetSamlPageUrl() const {
EXPECT_TRUE(saml_server_.Started());
return saml_server_.GetURL(std::string("/") + kSamlPageUrlPath);
}
void TestClientCertSamlIdpMixin::SetUpOnMainThread() {
ASSERT_TRUE(saml_server_.Start());
ASSERT_TRUE(saml_with_client_certs_server_.Start());
}
std::unique_ptr<net::test_server::HttpResponse>
TestClientCertSamlIdpMixin::HandleSamlServerRequest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().ExtractFileName() != kSamlPageUrlPath)
return nullptr;
// Extract the RelayState parameter specified by Gaia, so that we can pass
// this value to subsequent SAML pages and finally back to Gaia.
std::string saml_relay_state;
EXPECT_TRUE(net::GetValueForKeyInQuery(
request.GetURL(), kSamlRelayStateUrlParam, &saml_relay_state));
// Redirect to the second SAML page.
// TODO(crbug.com/1034451): Remove this HTML-based redirect (or even the
// whole first SAML page) from the test once the Login Screen implementation
// is fixed to support the client certificates on the very first SAML page.
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
GURL redirect_url = saml_with_client_certs_server_.GetURL(
std::string("/") + kSamlWithClientCertsPageUrlPath);
redirect_url = net::AppendQueryParameter(
redirect_url, kSamlRelayStateUrlParam, saml_relay_state);
response->set_content(base::ReplaceStringPlaceholders(
R"(<!doctype html><html><head>
<meta http-equiv="refresh" content="0; URL=$1" /></head></html>)",
{redirect_url.spec()}, /*offsets=*/nullptr));
return response;
}
std::unique_ptr<net::test_server::HttpResponse>
TestClientCertSamlIdpMixin::HandleSamlWithClientCertsServerRequest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().ExtractFileName() != kSamlWithClientCertsPageUrlPath)
return nullptr;
// Obtain the RelayState parameter that was originally specified by Gaia.
std::string saml_relay_state;
EXPECT_TRUE(net::GetValueForKeyInQuery(
request.GetURL(), kSamlRelayStateUrlParam, &saml_relay_state));
// Redirect to the Gaia SAML assertion page.
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_TEMPORARY_REDIRECT);
response->AddCustomHeader("Location",
GetGaiaSamlAssertionUrl(saml_relay_state).spec());
return response;
}
// Returns the URL to be used by the SAML page to redirect back to Gaia after
// the authentication completion.
GURL TestClientCertSamlIdpMixin::GetGaiaSamlAssertionUrl(
const std::string& saml_relay_state) {
GURL assertion_url =
gaia_mixin_->gaia_https_forwarder()->GetURLForSSLHost("").Resolve("/SSO");
assertion_url =
net::AppendQueryParameter(assertion_url, "SAMLResponse", kSamlResponse);
assertion_url = net::AppendQueryParameter(
assertion_url, kSamlRelayStateUrlParam, saml_relay_state);
return assertion_url;
}
} // namespace chromeos
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SAML_TEST_CLIENT_CERT_SAML_IDP_MIXIN_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_SAML_TEST_CLIENT_CERT_SAML_IDP_MIXIN_H_
#include <memory>
#include <string>
#include <vector>
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
class GURL;
namespace net {
namespace test_server {
struct HttpRequest;
class HttpResponse;
} // namespace test_server
} // namespace net
namespace chromeos {
class FakeGaiaMixin;
class TestClientCertSamlIdpMixin final : public InProcessBrowserTestMixin {
public:
// |client_cert_authorities| is the list of DER-encoded X.509
// DistinguishedName of certificate authorities that should be requested by
// the SAML server during the client authentication.
TestClientCertSamlIdpMixin(
InProcessBrowserTestMixinHost* host,
FakeGaiaMixin* gaia_mixin,
const std::vector<std::string>& client_cert_authorities);
TestClientCertSamlIdpMixin(const TestClientCertSamlIdpMixin&) = delete;
TestClientCertSamlIdpMixin& operator=(const TestClientCertSamlIdpMixin&) =
delete;
~TestClientCertSamlIdpMixin() override;
// Returns the SAML IdP initial page URL, which should be configured in the
// (fake) Gaia as the redirect endpoint for the tested users.
GURL GetSamlPageUrl() const;
// InProcessBrowserTestMixin:
void SetUpOnMainThread() override;
private:
// Handles requests to |saml_server_|.
std::unique_ptr<net::test_server::HttpResponse> HandleSamlServerRequest(
const net::test_server::HttpRequest& request);
// Handles requests to |saml_with_client_certs_server_|.
std::unique_ptr<net::test_server::HttpResponse>
HandleSamlWithClientCertsServerRequest(
const net::test_server::HttpRequest& request);
// Returns the URL to be used by the SAML page to redirect back to Gaia after
// the authentication completion.
GURL GetGaiaSamlAssertionUrl(const std::string& saml_relay_state);
FakeGaiaMixin* const gaia_mixin_;
net::EmbeddedTestServer saml_server_{net::EmbeddedTestServer::TYPE_HTTPS};
net::EmbeddedTestServer saml_with_client_certs_server_{
net::EmbeddedTestServer::TYPE_HTTPS};
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_SAML_TEST_CLIENT_CERT_SAML_IDP_MIXIN_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/users/test_users.h"
namespace chromeos {
namespace saml_test_users {
const char kFirstUserCorpExampleComEmail[] = "alice@corp.example.com";
const char kSecondUserCorpExampleComEmail[] = "bob@corp.example.com";
const char kThirdUserCorpExampleComEmail[] = "carol@corp.example.com";
const char kFourthUserCorpExampleTestEmail[] = "dan@corp.example.test";
const char kFifthUserExampleTestEmail[] = "eve@example.test";
} // namespace saml_test_users
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Constants for Chrome OS test user accounts.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_USERS_TEST_USERS_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_USERS_TEST_USERS_H_
namespace chromeos {
namespace saml_test_users {
// Note that the "corp.example.com" and the "example.test" domains are
// important, since they're hardcoded in embedded_setup_chromeos.html.
extern const char kFirstUserCorpExampleComEmail[];
extern const char kSecondUserCorpExampleComEmail[];
extern const char kThirdUserCorpExampleComEmail[];
extern const char kFourthUserCorpExampleTestEmail[];
extern const char kFifthUserExampleTestEmail[];
} // namespace saml_test_users
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_USERS_TEST_USERS_H_
......@@ -1491,6 +1491,8 @@ Polymer({
// initialization.
return;
}
if (oldValue === null && newValue !== null)
chrome.send('securityTokenPinDialogShownForTest');
if ((oldValue !== null && newValue === null) ||
(oldValue !== null && newValue !== null &&
!this.pinDialogResultReported_)) {
......
......@@ -45,7 +45,7 @@
</div>
<div slot="bottom-buttons" class="layout horizontal justified">
<oobe-back-button on-tap="onBackClicked_"></oobe-back-button>
<oobe-next-button on-tap="onSubmit_"
<oobe-next-button id="submit" on-tap="onSubmit_"
disabled="[[!canSubmit_]]"></oobe-next-button>
</div>
</oobe-dialog>
......
......@@ -166,7 +166,10 @@ Polymer({
this.processingCompletion_ = false;
this.hasValue_ = false;
this.userEdited_ = false;
this.$.pinKeyboard.focusInput();
// Note: setting the focus synchronously, to avoid flakiness in tests due to
// racing between the asynchronous caret positioning and the PIN characters
// input.
this.$.pinKeyboard.focusInputSynchronously();
},
/**
......
......@@ -887,6 +887,8 @@ if (!is_android) {
"../browser/chrome_worker_browsertest.cc",
"../browser/chromeos/certificate_provider/test_certificate_provider_extension_login_screen_mixin.cc",
"../browser/chromeos/certificate_provider/test_certificate_provider_extension_login_screen_mixin.h",
"../browser/chromeos/login/saml/test_client_cert_saml_idp_mixin.cc",
"../browser/chromeos/login/saml/test_client_cert_saml_idp_mixin.h",
"../browser/chromeos/scoped_test_system_nss_key_slot_mixin.cc",
"../browser/chromeos/scoped_test_system_nss_key_slot_mixin.h",
"../browser/client_hints/client_hints_browsertest.cc",
......@@ -2243,6 +2245,7 @@ if (!is_android) {
"../browser/chromeos/login/reset_browsertest.cc",
"../browser/chromeos/login/saml/password_change_success_detection_browsertest.cc",
"../browser/chromeos/login/saml/saml_browsertest.cc",
"../browser/chromeos/login/saml/security_token_saml_browsertest.cc",
"../browser/chromeos/login/screens/app_downloading_screen_browsertest.cc",
"../browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc",
"../browser/chromeos/login/screens/fingerprint_setup_browsertest.cc",
......
......@@ -30,34 +30,79 @@ function jsonifiableFromSignRequest(signRequest) {
return transformedSignRequest;
}
// Listener for the chrome.certificateProvider.onCertificatesRequested event.
function onCertificatesRequested(reportCallback) {
requestCertificatesFromCpp(reportCallback);
}
// Listener for the chrome.certificateProvider.onSignDigestRequested event.
function onSignDigestRequested(request, reportCallback) {
requestSignatureFromCpp(request, /*pinUserInput=*/ null, reportCallback);
}
function requestCertificatesFromCpp(reportCertificatesCallback) {
chrome.test.sendMessage(
JSON.stringify(['onCertificatesRequested']),
onCertificatesResponseFromCpp.bind(null, reportCertificatesCallback));
}
function onCertificatesResponseFromCpp(reportCertificatesCallback, response) {
const certInfoList = certInfoListFromParsedJson(JSON.parse(response));
reportCertificatesCallback(certInfoList, rejectedCertificates => {
if (rejectedCertificates && rejectedCertificates.length) {
console.error(
'Rejected certificates: ' + JSON.stringify(rejectedCertificates));
}
});
}
function requestSignatureFromCpp(
signDigestRequest, pinUserInput, reportSignatureCallback) {
chrome.test.sendMessage(
JSON.stringify([
'onSignatureRequested', jsonifiableFromSignRequest(signDigestRequest),
pinUserInput
]),
onSignatureResponseFromCpp.bind(
null, signDigestRequest, reportSignatureCallback));
}
function onSignatureResponseFromCpp(
signDigestRequest, reportSignatureCallback, response) {
const parsedResponse = JSON.parse(response);
if (parsedResponse === null) {
// The C++ handler signaled an error.
reportSignatureCallback();
return;
}
if (parsedResponse.stopPinRequest) {
// The C++ handler asked to stop the PIN request.
chrome.certificateProvider.stopPinRequest(
parsedResponse.stopPinRequest, function() {});
// Note that we're not returning here, since the parsed response may contain
// the signature as well.
}
if (parsedResponse.signature) {
// Forward the signature generated by the C++ handler.
reportSignatureCallback(arrayBufferFromByteList(parsedResponse.signature));
}
if (parsedResponse.requestPin) {
// The C++ handler asked to request the PIN. After the PIN is obtained,
// we'll request the signature from the C++ handler again.
chrome.certificateProvider.requestPin(
parsedResponse.requestPin, requestPinResponse => {
const pinUserInput =
requestPinResponse && requestPinResponse.userInput ?
requestPinResponse.userInput :
'pin-request-failed';
requestSignatureFromCpp(
signDigestRequest, pinUserInput, reportSignatureCallback);
});
}
}
chrome.certificateProvider.onCertificatesRequested.addListener(
reportCallback => {
// Forward the request to the C++ handler.
chrome.test.sendMessage(
JSON.stringify(['onCertificatesRequested']), response => {
const certInfoList =
certInfoListFromParsedJson(JSON.parse(response));
reportCallback(certInfoList, rejectedCertificates => {
if (rejectedCertificates && rejectedCertificates.length) {
console.error(
'Rejected certificates: ' +
JSON.stringify(rejectedCertificates));
}
});
});
});
onCertificatesRequested);
chrome.certificateProvider.onSignDigestRequested.addListener(
(request, reportCallback) => {
// Forward the request to the C++ handler.
chrome.test.sendMessage(
JSON.stringify(
['onSignDigestRequested', jsonifiableFromSignRequest(request)]),
response => {
const parsedResponse = JSON.parse(response);
const signature = (parsedResponse === null) ?
undefined :
arrayBufferFromByteList(parsedResponse);
reportCallback(signature);
});
});
onSignDigestRequested);
......@@ -197,19 +197,19 @@
<div id="rowsContainer" on-mousedown="onRowContainerMousedown_">
<div class="row">
<cr-button class="digit-button" on-tap="onNumberTap_" value="1"
disabled="[[disabled]]" circle-ripple>
id="digitButton1" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard1')]]</inner-text>
<inner-text class="letter empty"
hidden="[[!enableLetters]]">ABC</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="2"
disabled="[[disabled]]" circle-ripple>
id="digitButton2" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard2')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">ABC</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="3"
disabled="[[disabled]]" circle-ripple>
id="digitButton3" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard3')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">DEF</inner-text>
......@@ -217,19 +217,19 @@
</div>
<div class="row">
<cr-button class="digit-button" on-tap="onNumberTap_" value="4"
disabled="[[disabled]]" circle-ripple>
id="digitButton4" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard4')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">GHI</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="5"
disabled="[[disabled]]" circle-ripple>
id="digitButton5" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard5')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">JKL</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="6"
disabled="[[disabled]]" circle-ripple>
id="digitButton6" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard6')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">MNO</inner-text>
......@@ -237,19 +237,19 @@
</div>
<div class="row">
<cr-button class="digit-button" on-tap="onNumberTap_" value="7"
disabled="[[disabled]]" circle-ripple>
id="digitButton7" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard7')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">PQRS</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="8"
disabled="[[disabled]]" circle-ripple>
id="digitButton8" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard8')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">TUV</inner-text>
</cr-button>
<cr-button class="digit-button" on-tap="onNumberTap_" value="9"
disabled="[[disabled]]" circle-ripple>
id="digitButton9" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard9')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">WXYZ</inner-text>
......@@ -258,7 +258,7 @@
<div class="row bottom-row">
<div class="digit-button"></div>
<cr-button class="digit-button" on-tap="onNumberTap_" value="0"
disabled="[[disabled]]" circle-ripple>
id="digitButton0" disabled="[[disabled]]" circle-ripple>
<inner-text class="number">[[i18n('pinKeyboard0')]]</inner-text>
<inner-text class="letter"
hidden="[[!enableLetters]]">+</inner-text>
......
......@@ -214,6 +214,18 @@ Polymer({
this.passwordElement_().blur();
},
/**
* Schedules a call to focusInputSynchronously().
* @param {number=} opt_selectionStart
* @param {number=} opt_selectionEnd
*/
focusInput(opt_selectionStart, opt_selectionEnd) {
setTimeout(
() =>
this.focusInputSynchronously(opt_selectionStart, opt_selectionEnd),
0);
},
/**
* Transfers focus to the input element. This should not bring up the virtual
* keyboard, if it is enabled. After focus, moves the caret to the correct
......@@ -221,12 +233,10 @@ Polymer({
* @param {number=} opt_selectionStart
* @param {number=} opt_selectionEnd
*/
focusInput(opt_selectionStart, opt_selectionEnd) {
setTimeout(function() {
this.passwordElement_().focus();
this.selectionStart_ = opt_selectionStart || 0;
this.selectionEnd_ = opt_selectionEnd || 0;
}.bind(this), 0);
focusInputSynchronously(opt_selectionStart, opt_selectionEnd) {
this.passwordElement_().focus();
this.selectionStart_ = opt_selectionStart || 0;
this.selectionEnd_ = opt_selectionEnd || 0;
},
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment