Commit b6bb0f23 authored by Maria Kazinova's avatar Maria Kazinova Committed by Commit Bot

Removing CredentialManager API on iOS. Part 1: removing tests.

Deleting the API and its tests, as the kCredentialManager feature was
never launched and there is no plan to launch it in the future.

Bug: 435048
Change-Id: Id295c436a8583d50aaa7cb727555f4888fd69a8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2401042Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Commit-Queue: Maria Kazinova <kazinova@google.com>
Cr-Commit-Position: refs/heads/master@{#806287}
parent f5e796e7
...@@ -64,7 +64,6 @@ source_set("unit_tests") { ...@@ -64,7 +64,6 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"account_select_fill_data_unittest.cc", "account_select_fill_data_unittest.cc",
"credential_manager_util_unittest.cc",
"password_form_helper_unittest.mm", "password_form_helper_unittest.mm",
"shared_password_controller_unittest.mm", "shared_password_controller_unittest.mm",
] ]
......
// Copyright 2017 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 "components/password_manager/ios/credential_manager_util.h"
#include <memory>
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
namespace password_manager {
namespace {
constexpr char kTestWebOrigin[] = "https://example.com/";
constexpr char kTestIconUrl[] = "https://www.google.com/favicon.ico";
base::DictionaryValue BuildExampleCredential() {
base::DictionaryValue json;
json.SetString(kCredentialIdKey, "john@doe.com");
json.SetString(kCredentialNameKey, "John Doe");
json.SetString(kCredentialIconKey, kTestIconUrl);
return json;
}
// Builds a dictionary representing valid PasswordCredential.
base::DictionaryValue BuildExampleValidPasswordCredential() {
base::DictionaryValue json = BuildExampleCredential();
json.SetString(kPasswordCredentialPasswordKey, "admin123");
json.SetString(kCredentialTypeKey, kCredentialTypePassword);
return json;
}
// Builds a dictionary representing valid FederatedCredential.
base::DictionaryValue BuildExampleValidFederatedCredential() {
base::DictionaryValue json = BuildExampleCredential();
json.SetString(kFederatedCredentialProviderKey, kTestWebOrigin);
json.SetString(kCredentialTypeKey, kCredentialTypeFederated);
return json;
}
} // namespace
using CredentialManagerUtilTest = PlatformTest;
// Checks that CredentialRequestOptions.password field is parsed
// correctly.
TEST_F(CredentialManagerUtilTest, ParseIncludePasswords) {
base::DictionaryValue json;
bool include_passwords = true;
// Default value should be false.
EXPECT_TRUE(ParseIncludePasswords(json, &include_passwords));
EXPECT_FALSE(include_passwords);
// true/false values should be parsed correctly.
json.SetBoolean(kCredentialRequestPasswordKey, true);
EXPECT_TRUE(ParseIncludePasswords(json, &include_passwords));
EXPECT_TRUE(include_passwords);
json.SetBoolean(kCredentialRequestPasswordKey, false);
EXPECT_TRUE(ParseIncludePasswords(json, &include_passwords));
EXPECT_FALSE(include_passwords);
// Test against random string.
json.SetString(kCredentialRequestPasswordKey, "yes");
EXPECT_FALSE(ParseIncludePasswords(json, &include_passwords));
}
// Checks that CredentialRequestOptions.mediation field is parsed
// correctly.
TEST_F(CredentialManagerUtilTest, ParseMediationRequirement) {
base::DictionaryValue json;
CredentialMediationRequirement mediation;
// Default value should be kOptional.
EXPECT_TRUE(ParseMediationRequirement(json, &mediation));
EXPECT_EQ(CredentialMediationRequirement::kOptional, mediation);
// "silent"/"optional"/"required" values should be parsed correctly.
json.SetString(kCredentialRequestMediationKey, kMediationRequirementSilent);
EXPECT_TRUE(ParseMediationRequirement(json, &mediation));
EXPECT_EQ(CredentialMediationRequirement::kSilent, mediation);
json.SetString(kCredentialRequestMediationKey, kMediationRequirementOptional);
EXPECT_TRUE(ParseMediationRequirement(json, &mediation));
EXPECT_EQ(CredentialMediationRequirement::kOptional, mediation);
json.SetString(kCredentialRequestMediationKey, kMediationRequirementRequired);
EXPECT_TRUE(ParseMediationRequirement(json, &mediation));
EXPECT_EQ(CredentialMediationRequirement::kRequired, mediation);
// Test against random string.
json.SetString(kCredentialRequestMediationKey, "dksjl");
EXPECT_FALSE(ParseMediationRequirement(json, &mediation));
}
// Checks that Credential.type field is parsed correctly.
TEST_F(CredentialManagerUtilTest, ParseCredentialType) {
base::DictionaryValue json;
CredentialType type = CredentialType::CREDENTIAL_TYPE_EMPTY;
// JS object Credential must contain |type| field.
EXPECT_FALSE(ParseCredentialType(json, &type));
// "PasswordCredential"/"FederatedCredential" values should be parsed
// correctly.
json.SetString(kCredentialTypeKey, kCredentialTypePassword);
EXPECT_TRUE(ParseCredentialType(json, &type));
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_PASSWORD, type);
json.SetString(kCredentialTypeKey, kCredentialTypeFederated);
EXPECT_TRUE(ParseCredentialType(json, &type));
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_FEDERATED, type);
// "Credential" is not a valid type.
json.SetString(kCredentialTypeKey, "Credential");
EXPECT_FALSE(ParseCredentialType(json, &type));
// Empty string is also not allowed.
json.SetString(kCredentialTypeKey, "");
EXPECT_FALSE(ParseCredentialType(json, &type));
}
// Checks that common fields of PasswordCredential and FederatedCredential are
// parsed correctly.
TEST_F(CredentialManagerUtilTest, ParseCommonCredentialFields) {
// Building PasswordCredential because ParseCredentialDictionary for
// Credential containing only common fields would return false.
base::DictionaryValue json = BuildExampleValidPasswordCredential();
CredentialInfo credential;
std::string reason;
// Valid dictionary should be parsed correctly and ParseCredentialDictionary
// should return true.
EXPECT_TRUE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(base::ASCIIToUTF16("john@doe.com"), credential.id);
EXPECT_EQ(base::ASCIIToUTF16("John Doe"), credential.name);
EXPECT_EQ(GURL(kTestIconUrl), credential.icon);
// |id| field is required.
json.Remove(kCredentialIdKey, nullptr);
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'id' field");
// |iconURL| field is not required.
json = BuildExampleValidPasswordCredential();
json.Remove(kCredentialIconKey, nullptr);
EXPECT_TRUE(ParseCredentialDictionary(json, &credential, &reason));
// If Credential has |iconURL| field, it must be a valid URL.
json.SetString(kCredentialIconKey, "not a valid url");
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "iconURL is either invalid or insecure URL");
// If Credential has |iconURL| field, it must be a secure URL.
reason = std::string();
json.SetString(kCredentialIconKey, "http://example.com");
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "iconURL is either invalid or insecure URL");
// Check that empty |iconURL| field is treated as no |iconURL| field.
json.SetString(kCredentialIconKey, "");
EXPECT_TRUE(ParseCredentialDictionary(json, &credential, &reason));
}
// Checks that |password| and |type| fields of PasswordCredential are parsed
// correctly.
TEST_F(CredentialManagerUtilTest, ParsePasswordCredential) {
base::DictionaryValue json = BuildExampleValidPasswordCredential();
CredentialInfo credential;
std::string reason;
// Valid dictionary should be parsed correctly and ParseCredentialDictionary
// should return true.
EXPECT_TRUE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_PASSWORD, credential.type);
EXPECT_EQ(base::ASCIIToUTF16("admin123"), credential.password);
// |password| field is required.
json.Remove(kPasswordCredentialPasswordKey, nullptr);
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'password' field");
// |password| field is cannot be an empty string.
json.SetString(kPasswordCredentialPasswordKey, "");
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'password' field");
}
// Checks that |provider| and |type| fields of FederatedCredential are parsed
// correctly.
TEST_F(CredentialManagerUtilTest, ParseFederatedCredential) {
base::DictionaryValue json = BuildExampleValidFederatedCredential();
CredentialInfo credential;
std::string reason;
// Valid dictionary should be parsed correctly and ParseCredentialDictionary
// should return true.
EXPECT_TRUE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_FEDERATED, credential.type);
EXPECT_EQ(GURL(kTestWebOrigin), credential.federation.GetURL());
// |provider| field is required.
json.Remove(kFederatedCredentialProviderKey, nullptr);
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'provider' field");
// |provider| field cannot be an empty string.
json.SetString(kFederatedCredentialProviderKey, "");
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'provider' field");
// |provider| field must be a valid URL.
json.SetString(kFederatedCredentialProviderKey, "not a valid URL");
EXPECT_FALSE(ParseCredentialDictionary(json, &credential, &reason));
EXPECT_EQ(reason, "no valid 'provider' field");
}
// Checks that |providers| field of FederatedCredentialRequestOptions is
// parsed correctly.
TEST_F(CredentialManagerUtilTest, ParseFederations) {
base::DictionaryValue json;
// Build example valid |providers| list.
std::unique_ptr<base::ListValue> list_ptr =
std::make_unique<base::ListValue>();
list_ptr->Append(kTestWebOrigin);
list_ptr->Append("https://google.com");
json.SetList(kCredentialRequestProvidersKey, std::move(list_ptr));
std::vector<GURL> federations;
// Check that parsing valid |providers| results in correct |federations| list.
EXPECT_TRUE(ParseFederations(json, &federations));
EXPECT_THAT(federations, testing::ElementsAre(GURL(kTestWebOrigin),
GURL("https://google.com")));
// ParseFederations should skip invalid URLs.
list_ptr = std::make_unique<base::ListValue>();
list_ptr->Append(kTestWebOrigin);
list_ptr->Append("not a valid url");
json.SetList(kCredentialRequestProvidersKey, std::move(list_ptr));
EXPECT_TRUE(ParseFederations(json, &federations));
EXPECT_THAT(federations, testing::ElementsAre(GURL(kTestWebOrigin)));
// If |providers| is not a valid list, ParseFederations should return false.
json.SetString(kCredentialRequestProvidersKey, kTestWebOrigin);
EXPECT_FALSE(ParseFederations(json, &federations));
}
} // namespace password_manager
...@@ -171,10 +171,8 @@ source_set("unit_tests") { ...@@ -171,10 +171,8 @@ source_set("unit_tests") {
configs += [ "//build/config/compiler:enable_arc" ] configs += [ "//build/config/compiler:enable_arc" ]
testonly = true testonly = true
sources = [ sources = [
"credential_manager_unittest.mm",
"ios_chrome_password_check_manager_unittest.mm", "ios_chrome_password_check_manager_unittest.mm",
"ios_chrome_password_manager_client_unittest.mm", "ios_chrome_password_manager_client_unittest.mm",
"js_credential_manager_unittest.mm",
"password_controller_js_unittest.mm", "password_controller_js_unittest.mm",
"password_controller_unittest.mm", "password_controller_unittest.mm",
"well_known_change_password_tab_helper_unittest.mm", "well_known_change_password_tab_helper_unittest.mm",
...@@ -196,13 +194,10 @@ source_set("unit_tests") { ...@@ -196,13 +194,10 @@ source_set("unit_tests") {
"//components/prefs", "//components/prefs",
"//components/prefs:test_support", "//components/prefs:test_support",
"//components/safe_browsing/core/common:safe_browsing_prefs", "//components/safe_browsing/core/common:safe_browsing_prefs",
"//components/security_state/ios",
"//components/ukm:test_support", "//components/ukm:test_support",
"//google_apis",
"//ios/chrome/browser/autofill", "//ios/chrome/browser/autofill",
"//ios/chrome/browser/browser_state:test_support", "//ios/chrome/browser/browser_state:test_support",
"//ios/chrome/browser/passwords/test", "//ios/chrome/browser/passwords/test",
"//ios/chrome/browser/ssl",
"//ios/chrome/browser/ui/autofill/form_input_accessory", "//ios/chrome/browser/ui/autofill/form_input_accessory",
"//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/web:test_support", "//ios/chrome/browser/web:test_support",
...@@ -210,7 +205,6 @@ source_set("unit_tests") { ...@@ -210,7 +205,6 @@ source_set("unit_tests") {
"//ios/testing:ocmock_support", "//ios/testing:ocmock_support",
"//ios/web", "//ios/web",
"//ios/web/public/js_messaging", "//ios/web/public/js_messaging",
"//ios/web/public/security",
"//ios/web/public/test", "//ios/web/public/test",
"//ios/web/public/test/fakes", "//ios/web/public/test/fakes",
"//net:test_support", "//net:test_support",
...@@ -228,10 +222,8 @@ source_set("eg2_tests") { ...@@ -228,10 +222,8 @@ source_set("eg2_tests") {
"//build/config/ios:xctest_config", "//build/config/ios:xctest_config",
] ]
testonly = true testonly = true
sources = [ "credential_manager_egtest.mm" ]
deps = [ deps = [
":eg_test_support+eg2", ":eg_test_support+eg2",
":feature_flags",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/password_manager/core/common", "//components/password_manager/core/common",
......
// Copyright 2017 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.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#include <memory>
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#import "ios/chrome/browser/passwords/password_manager_app_interface.h"
#include "ios/chrome/browser/passwords/password_manager_features.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/disabled_test_macros.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/web/public/test/http_server/http_server.h"
#include "ios/web/public/test/http_server/http_server_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#if defined(CHROME_EARL_GREY_2)
// TODO(crbug.com/1015113): The EG2 macro is breaking indexing for some reason
// without the trailing semicolon. For now, disable the extra semi warning
// so Xcode indexing works for the egtest.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++98-compat-extra-semi"
GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(PasswordManagerAppInterface);
#endif // defined(CHROME_EARL_GREY_2)
namespace {
// Notification should be displayed for 3 seconds. 4 should be safe to check.
constexpr CFTimeInterval kDisappearanceTimeout = 4;
// Provides basic response for example page.
std::unique_ptr<net::test_server::HttpResponse> StandardResponse(
const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content(
"<head><title>Example website</title></head>"
"<body>You are here.</body>");
return std::move(http_response);
}
} // namespace
// This class tests UI behavior for Credential Manager.
// TODO(crbug.com/435048): Add EG test for save/update password prompt.
// TODO(crbug.com/435048): When account chooser and first run experience dialog
// are implemented, test them too.
@interface CredentialManagerEGTest : ChromeTestCase
@end
@implementation CredentialManagerEGTest {
base::test::ScopedFeatureList _featureList;
}
- (void)setUp {
_featureList.InitAndEnableFeature(features::kCredentialManager);
[super setUp];
// Set up server.
self.testServer->RegisterRequestHandler(base::Bind(&StandardResponse));
GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
}
- (void)tearDown {
[PasswordManagerAppInterface clearCredentials];
[super tearDown];
}
- (AppLaunchConfiguration)appConfigurationForTestCase {
AppLaunchConfiguration config;
config.features_enabled.push_back(features::kCredentialManager);
return config;
}
#pragma mark - Utils
// Loads simple page on localhost and stores an example PasswordCredential.
- (void)loadSimplePageAndStoreACredential {
// Loads simple page. It is on localhost so it is considered a secure context.
const GURL URL = self.testServer->GetURL("/example");
[ChromeEarlGrey loadURL:URL];
[ChromeEarlGrey waitForWebStateContainingText:"You are here."];
NSError* error = [PasswordManagerAppInterface
storeCredentialWithUsername:@"johndoe@example.com"
password:@"ilovejanedoe123"];
GREYAssertNil(error, error.localizedDescription);
}
#pragma mark - Tests
// Tests that notification saying "Signing is as ..." appears on auto sign-in.
- (void)testNotificationAppearsOnAutoSignIn {
// TODO(crbug.com/786960): re-enable when fixed. Tests may pass on EG2
#if defined(CHROME_EARL_GREY_1)
EARL_GREY_TEST_DISABLED(@"Fails on iOS 11.0.");
#endif
// Set Autosignin preferences
[ChromeEarlGrey setBoolValue:YES
forUserPref:password_manager::prefs::
kWasAutoSignInFirstRunExperienceShown];
[ChromeEarlGrey
setBoolValue:YES
forUserPref:password_manager::prefs::kCredentialsEnableAutosignin];
[self loadSimplePageAndStoreACredential];
// Call get() from JavaScript.
NSError* error = nil;
NSString* result = [ChromeEarlGreyAppInterface
executeJavaScript:@"typeof navigator.credentials.get({password: true})"
error:&error];
GREYAssertTrue([result isEqual:@"object"],
@"Unexpected error occurred when executing JavaScript.");
GREYAssertTrue(!error,
@"Unexpected error occurred when executing JavaScript.");
// Matches the UILabel by its accessibilityLabel.
id<GREYMatcher> matcher =
grey_allOf(grey_accessibilityLabel(@"Signing in as johndoe@example.com"),
grey_accessibilityTrait(UIAccessibilityTraitStaticText), nil);
// Wait for notification to appear.
ConditionBlock waitForAppearance = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
};
// Gives some time for the notification to appear.
GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForUIElementTimeout, waitForAppearance),
@"Notification did not appear");
// Wait for the notification to disappear.
ConditionBlock waitForDisappearance = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
return error == nil;
};
// Ensures that notification disappears after time limit.
GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(kDisappearanceTimeout,
waitForDisappearance),
@"Notification did not disappear");
}
// Tests that when navigator.credentials.get() was called from inactive tab, the
// autosign-in notification appears once tab becomes active.
- (void)testNotificationAppearsWhenTabIsActive {
// TODO(crbug.com/786960): re-enable when fixed. Tests may pass on EG2
#if defined(CHROME_EARL_GREY_1)
EARL_GREY_TEST_DISABLED(@"Fails on iOS 11.0.");
#endif
// Set Autosignin preferences
[ChromeEarlGrey setBoolValue:YES
forUserPref:password_manager::prefs::
kWasAutoSignInFirstRunExperienceShown];
[ChromeEarlGrey
setBoolValue:YES
forUserPref:password_manager::prefs::kCredentialsEnableAutosignin];
[self loadSimplePageAndStoreACredential];
// Open new tab.
[ChromeEarlGreyUI openNewTab];
[ChromeEarlGrey waitForMainTabCount:2];
[PasswordManagerAppInterface getCredentialsInTabAtIndex:0];
// Matches the UILabel by its accessibilityLabel.
id<GREYMatcher> matcher = chrome_test_util::StaticTextWithAccessibilityLabel(
@"Signing in as johndoe@example.com");
// Wait for notification to appear.
ConditionBlock waitForAppearance = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
return error == nil;
};
// Check that notification doesn't appear in current tab.
GREYAssertFalse(
base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForUIElementTimeout, waitForAppearance),
@"Notification appeared in wrong tab");
// Switch to previous tab.
[ChromeEarlGrey selectTabAtIndex:0];
// Check that the notification has appeared.
GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForUIElementTimeout, waitForAppearance),
@"Notification did not appear");
}
@end
// Copyright 2017 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.
#import "ios/chrome/browser/passwords/credential_manager.h"
#include <memory>
#include "base/bind.h"
#include "base/mac/foundation_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/password_manager/core/browser/leak_detection/leak_detection_check.h"
#include "components/password_manager/core/browser/leak_detection/leak_detection_check_factory.h"
#include "components/password_manager/core/browser/leak_detection/mock_leak_detection_check_factory.h"
#include "components/password_manager/core/browser/test_password_store.h"
#include "components/password_manager/ios/credential_manager_util.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "ios/chrome/browser/passwords/test/test_password_manager_client.h"
#include "ios/web/public/navigation/navigation_item.h"
#include "ios/web/public/navigation/navigation_manager.h"
#include "ios/web/public/security/ssl_status.h"
#import "ios/web/public/test/web_js_test.h"
#include "ios/web/public/test/web_test_with_web_state.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using testing::_;
using url::Origin;
namespace {
// Test hostname for cert verification.
constexpr char kHostName[] = "www.example.com";
// HTTPS origin corresponding to kHostName.
constexpr char kHttpsWebOrigin[] = "https://www.example.com/";
// HTTP origin corresponding to kHostName.
constexpr char kHttpWebOrigin[] = "http://www.example.com/";
// HTTP origin representing localhost. It should be considered secure.
constexpr char kLocalhostOrigin[] = "http://localhost";
// Origin with data scheme. It should be considered insecure.
constexpr char kDataUriSchemeOrigin[] = "data://www.example.com";
// File origin.
constexpr char kFileOrigin[] = "file://example_file";
// SSL certificate to load for testing.
constexpr char kCertFileName[] = "ok_cert.pem";
class MockLeakDetectionCheck : public password_manager::LeakDetectionCheck {
public:
MOCK_METHOD3(Start, void(const GURL&, base::string16, base::string16));
};
} // namespace
class CredentialManagerBaseTest
: public web::WebJsTest<web::WebTestWithWebState> {
public:
CredentialManagerBaseTest()
: web::WebJsTest<web::WebTestWithWebState>(@[ @"credential_manager" ]) {}
void SetUp() override {
WebTestWithWebState::SetUp();
}
// Updates SSLStatus on web_state()->GetNavigationManager()->GetVisibleItem()
// with given |cert_status|, |security_style| and |content_status|.
// SSLStatus fields |certificate|, |connection_status| and |cert_status_host|
// are the same for all tests.
void UpdateSslStatus(net::CertStatus cert_status,
web::SecurityStyle security_style,
web::SSLStatus::ContentStatusFlags content_status) {
scoped_refptr<net::X509Certificate> cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), kCertFileName);
web::SSLStatus& ssl =
web_state()->GetNavigationManager()->GetVisibleItem()->GetSSL();
ssl.security_style = security_style;
ssl.certificate = cert;
ssl.cert_status = cert_status;
ssl.content_status = content_status;
ssl.cert_status_host = kHostName;
}
};
// Tests CredentialManager class. Tests are performed as follows:
// 1. CredentialManager is instantiated. In its constructor it registers a
// script command callback for 'credentials' prefix.
// 2. credential_manager.js is injected to the web page.
// 3. JavaScript code invoking one of exposed API methods is executed.
// 4. This results in CredentialManager::HandleScriptCommand being called.
// 5. Wait for background tasks to finish, optionally for returned Promise to be
// resolved or rejected.
// 6. Check values in JavaScript, stored in variables under 'test_*' prefix by
// resolver/rejecter functions.
// 7. Optionally expect PasswordManagerClient methods to be (not) called.
class CredentialManagerTest : public CredentialManagerBaseTest {
public:
void SetUp() override {
CredentialManagerBaseTest::SetUp();
client_ = std::make_unique<TestPasswordManagerClient>();
manager_ = std::make_unique<CredentialManager>(client_.get(), web_state());
// Inject JavaScript and set up secure context.
LoadHtml(@"<html></html>", GURL(kHttpsWebOrigin));
LoadHtmlAndInject(@"<html></html>");
UpdateSslStatus(net::CERT_STATUS_IS_EV, web::SECURITY_STYLE_AUTHENTICATED,
web::SSLStatus::NORMAL_CONTENT);
password_credential_form_1_.username_value = base::ASCIIToUTF16("id1");
password_credential_form_1_.display_name = base::ASCIIToUTF16("Name One");
password_credential_form_1_.icon_url = GURL("https://example.com/icon.png");
password_credential_form_1_.password_value = base::ASCIIToUTF16("secret1");
password_credential_form_1_.url = GURL(kHttpsWebOrigin);
password_credential_form_1_.signon_realm = kHttpsWebOrigin;
password_credential_form_1_.scheme = autofill::PasswordForm::Scheme::kHtml;
password_credential_form_2_.username_value = base::ASCIIToUTF16("id2");
password_credential_form_2_.display_name = base::ASCIIToUTF16("Name Two");
password_credential_form_2_.icon_url = GURL("https://example.com/icon.png");
password_credential_form_2_.password_value = base::ASCIIToUTF16("secret2");
password_credential_form_2_.url = GURL(kHttpsWebOrigin);
password_credential_form_2_.signon_realm = kHttpsWebOrigin;
password_credential_form_2_.scheme = autofill::PasswordForm::Scheme::kHtml;
federated_credential_form_.username_value = base::ASCIIToUTF16("id");
federated_credential_form_.display_name = base::ASCIIToUTF16("name");
federated_credential_form_.icon_url =
GURL("https://federation.com/icon.png");
federated_credential_form_.federation_origin =
Origin::Create(GURL("https://federation.com"));
federated_credential_form_.url = GURL(kHttpsWebOrigin);
federated_credential_form_.signon_realm =
"federation://www.example.com/www.federation.com";
federated_credential_form_.scheme = autofill::PasswordForm::Scheme::kHtml;
}
void TearDown() override {
manager_.reset();
// Shutdown PasswordStore.
if (client_->password_store()) {
client_->password_store()->ShutdownOnUIThread();
}
CredentialManagerBaseTest::TearDown();
}
protected:
std::unique_ptr<TestPasswordManagerClient> client_;
std::unique_ptr<CredentialManager> manager_;
autofill::PasswordForm password_credential_form_1_;
autofill::PasswordForm password_credential_form_2_;
autofill::PasswordForm federated_credential_form_;
};
// Tests storing a PasswordCredential.
TEST_F(CredentialManagerTest, StorePasswordCredential) {
auto mock_factory = std::make_unique<
testing::StrictMock<password_manager::MockLeakDetectionCheckFactory>>();
auto* weak_factory = mock_factory.get();
manager_->set_leak_factory(std::move(mock_factory));
auto check_instance = std::make_unique<MockLeakDetectionCheck>();
EXPECT_CALL(*check_instance,
Start(GURL(kHttpsWebOrigin), base::ASCIIToUTF16("id"),
base::ASCIIToUTF16("pencil")));
EXPECT_CALL(*weak_factory, TryCreateLeakCheck)
.WillOnce(testing::Return(testing::ByMove(std::move(check_instance))));
// Call API method |store|.
ExecuteJavaScript(
@"var credential = new PasswordCredential({"
" id: 'id',"
" password: 'pencil',"
" name: 'name',"
" iconURL: 'https://example.com/icon.png'"
"});"
"navigator.credentials.store(credential).then(function(result) {"
" test_result_ = (result == undefined);"
" test_promise_resolved_ = true;"
"});");
// Wait for the Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Check that Promise was resolved with undefined.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_"));
// Wait for credential to be stored.
WaitForBackgroundTasks();
client_->pending_manager()->Save();
WaitForBackgroundTasks();
EXPECT_FALSE(client_->password_store()->IsEmpty());
// Get the stored credential and check its fields.
TestPasswordStore::PasswordMap passwords =
client_->password_store()->stored_passwords();
EXPECT_EQ(1u, passwords.size());
EXPECT_EQ(1u, passwords[kHttpsWebOrigin].size());
autofill::PasswordForm form = passwords[kHttpsWebOrigin][0];
EXPECT_EQ(base::ASCIIToUTF16("id"), form.username_value);
EXPECT_EQ(base::ASCIIToUTF16("name"), form.display_name);
EXPECT_EQ(base::ASCIIToUTF16("pencil"), form.password_value);
EXPECT_EQ(GURL("https://example.com/icon.png"), form.icon_url);
EXPECT_EQ(GURL(kHttpsWebOrigin), form.url);
EXPECT_EQ(GURL(kHttpsWebOrigin), form.signon_realm);
}
// Tests storing a FederatedCredential.
TEST_F(CredentialManagerTest, StoreFederatedCredential) {
// Call API method |store|.
ExecuteJavaScript(
@"var credential = new FederatedCredential({"
" id: 'id',"
" provider: 'https://www.federation.com/',"
" name: 'name',"
" iconURL: 'https://federation.com/icon.png'"
"});"
"navigator.credentials.store(credential).then(function(result) {"
" test_result_ = (result == undefined);"
" test_promise_resolved_ = true;"
"});");
// Wait for the Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Check that Promise was resolved with undefined.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_"));
// Wait for credential to be stored.
WaitForBackgroundTasks();
client_->pending_manager()->Save();
WaitForBackgroundTasks();
EXPECT_FALSE(client_->password_store()->IsEmpty());
// Get the stored credential and check its fields.
TestPasswordStore::PasswordMap passwords =
client_->password_store()->stored_passwords();
EXPECT_EQ(1u, passwords.size());
std::string federated_origin =
"federation://www.example.com/www.federation.com";
EXPECT_EQ(1u, passwords[federated_origin].size());
autofill::PasswordForm form = passwords[federated_origin][0];
EXPECT_EQ(base::ASCIIToUTF16("id"), form.username_value);
EXPECT_EQ(base::ASCIIToUTF16("name"), form.display_name);
EXPECT_EQ(Origin::Create(GURL("https://www.federation.com")),
form.federation_origin);
EXPECT_EQ(GURL("https://federation.com/icon.png"), form.icon_url);
EXPECT_EQ(GURL("https://www.example.com"), form.url);
EXPECT_EQ(federated_origin, form.signon_realm);
}
// Tests that storing a credential from insecure context will not happen.
TEST_F(CredentialManagerTest, TryToStoreCredentialFromInsecureContext) {
// Inject JavaScript, set up WebState to have mixed content.
LoadHtml(@"<html></html>", GURL(kHttpsWebOrigin));
LoadHtmlAndInject(@"<html></html>");
UpdateSslStatus(net::CERT_STATUS_IS_EV, web::SECURITY_STYLE_AUTHENTICATED,
web::SSLStatus::DISPLAYED_INSECURE_CONTENT);
// Expect that user will NOT be prompted to save or update password.
EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)).Times(0);
// Call API method |store|.
ExecuteJavaScript(
@"var credential = new PasswordCredential({"
" id: 'id',"
" password: 'pencil',"
" name: 'name',"
" iconURL: 'https://example.com/icon.png'"
"});"
"navigator.credentials.store(credential).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof DOMException && "
" reason.name == DOMException.INVALID_STATE_ERR);"
" test_promise_rejected_ = true;"
"});");
WaitForBackgroundTasks();
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with InvalidStateError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
}
// Tests that Promise will be rejected with TypeError for invalid Credential.
TEST_F(CredentialManagerTest, RejectOnInvalidCredential) {
// Call |store| with invalid Credential: |iconURL| is not a valid URL.
ExecuteJavaScript(
@"var credential = new PasswordCredential({"
" id: 'id',"
" password: 'pencil',"
" name: 'name',"
" iconURL: 'https://'"
"});"
"navigator.credentials.store(credential).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof TypeError);"
" test_promise_rejected_ = true;"
"});");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with TypeError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
}
// Tests retrieving a PasswordCredential.
TEST_F(CredentialManagerTest, GetPasswordCredential) {
// Manually store a PasswordForm in password store.
client_->password_store()->AddLogin(password_credential_form_1_);
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
" mediation: 'silent'"
"}).then(function(credential) {"
" test_credential_ = credential; "
" test_promise_resolved_ = true;"
"})");
// Wait for PasswordCredential to be obtained and for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Check PasswordCredential fields.
ASSERT_NSEQ(@"password", ExecuteJavaScript(@"test_credential_.type"));
ASSERT_NSEQ(@"id1", ExecuteJavaScript(@"test_credential_.id"));
ASSERT_NSEQ(@"Name One", ExecuteJavaScript(@"test_credential_.name"));
ASSERT_NSEQ(@"secret1", ExecuteJavaScript(@"test_credential_.password"));
ASSERT_NSEQ(@"https://example.com/icon.png",
ExecuteJavaScript(@"test_credential_.iconURL"));
}
// Tests retrieving a FederatedCredential.
TEST_F(CredentialManagerTest, GetFederatedCredential) {
// Manually store a PasswordForm in password store.
client_->password_store()->AddLogin(federated_credential_form_);
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" providers: ['https://federation.com'], "
" mediation: 'silent'"
"}).then(function(credential) {"
" test_credential_ = credential;"
" test_promise_resolved_ = true;"
"})");
// Wait for FederatedCredential to be obtained and for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Check FederatedCredential fields.
ASSERT_NSEQ(@"federated", ExecuteJavaScript(@"test_credential_.type"));
ASSERT_NSEQ(@"id", ExecuteJavaScript(@"test_credential_.id"));
ASSERT_NSEQ(@"name", ExecuteJavaScript(@"test_credential_.name"));
ASSERT_NSEQ(@"https://federation.com",
ExecuteJavaScript(@"test_credential_.provider"));
ASSERT_NSEQ(@"https://federation.com/icon.png",
ExecuteJavaScript(@"test_credential_.iconURL"));
}
// Tests that requesting a credential from insecure context will not happen.
TEST_F(CredentialManagerTest, TryToGetCredentialFromInsecureContext) {
// Set up WebState to have non-cryptographic scheme.
LoadHtml(@"<html></html>", GURL(kHttpWebOrigin));
LoadHtmlAndInject(@"<html></html>");
client_->set_current_url(GURL(kHttpWebOrigin));
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
" mediation: 'required'"
"}).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof DOMException && "
" reason.name == DOMException.INVALID_STATE_ERR);"
" test_promise_rejected_ = true;"
"});");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with InvalidStateError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
}
// Tests that when Credential is requested with required mediation, a prompt to
// choose credential will be shown to the user.
TEST_F(CredentialManagerTest, GetCredentialWithRequiredMediation) {
// Manually store a PasswordForm in password store.
client_->password_store()->AddLogin(password_credential_form_1_);
// Expect that user will be prompted to choose credentials.
EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _));
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
" mediation: 'required'"
"}).then(function(credential) {"
" test_credential_ = credential; "
" test_promise_resolved_ = true;"
"})");
// Wait for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Expect that returned Credential is not null.
EXPECT_NSEQ(@"object", ExecuteJavaScript(@"typeof test_credential_"));
}
// Tests that Promise returned by |navigator.credentials.get| will resolve with
// |null| if PasswordStore is empty.
TEST_F(CredentialManagerTest, NullCredentialFromEmptyPasswordStore) {
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
" mediation: 'silent'"
"}).then(function(credential) {"
" test_credential_ = credential; "
" test_promise_resolved_ = true;"
"})");
// Wait for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Expect that returned Credential is null.
EXPECT_NSEQ(NULL, ExecuteJavaScript(@"test_credential_"));
}
// Tests that if multiple credentials are stored for a website and mediation
// requirement is set to 'optional', user will be prompted to choose
// credentials.
TEST_F(CredentialManagerTest, PromptUserOnMultipleCredentials) {
// Manually store two PasswordForms in password store.
client_->password_store()->AddLogin(password_credential_form_1_);
client_->password_store()->AddLogin(password_credential_form_2_);
// Expect that user will be prompted to choose credentials.
EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(_, _, _));
// Call API method |get|.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true, "
" mediation: 'optional'"
"}).then(function(credential) {"
" test_credential_ = credential;"
" test_promise_resolved_ = true;"
"})");
// Wait for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Expect that returned Credential is not null.
EXPECT_NSEQ(@"object", ExecuteJavaScript(@"typeof test_credential_"));
}
// Tests that Promise returned by |navigator.credentials.get| is rejected with
// TypeError if |mediation| value is invalid.
TEST_F(CredentialManagerTest, RejectOnInvalidMediationValue) {
// Call API method |get| with invalid |mediation| field.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
" mediation: 'maybe'"
"}).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof TypeError);"
" test_promise_rejected_ = true;"
"})");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with TypeError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
}
// Tests that Promise returned by |navigator.credentials.get| is rejected with
// TypeError if |providers| value is invalid.
TEST_F(CredentialManagerTest, RejectOnInvalidProvidersValue) {
// Call API method |get| with invalid |providers| field.
ExecuteJavaScript(
@"navigator.credentials.get({"
" providers: 'https://exampleprovider.com' /* not a list */"
"}).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof TypeError);"
" test_promise_rejected_ = true;"
"})");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with TypeError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
}
// Tests that Promise returned by |navigator.credentials.get| is rejected with
// NotSupportedError if password store is not available.
TEST_F(CredentialManagerTest, RejectOnPasswordStoreUnavailable) {
// Make password store unavailable.
client_->password_store()->ShutdownOnUIThread();
client_->set_password_store(nullptr);
// Call API method |get| with correct arguments, set up rejecter to store
// reason for failure in |test_*| variables.
ExecuteJavaScript(
@"navigator.credentials.get({"
" password: true,"
"}).catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof DOMException) && "
" (reason.name == DOMException.NOT_SUPPORTED_ERR);"
" test_result_message_ = reason.message;"
" test_promise_rejected_ = true;"
"})");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with NotSupportedError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
// Check that Promise message says "Password store is unavailable."
EXPECT_NSEQ(@"Password store is unavailable.",
ExecuteJavaScript(@"test_result_message_"));
}
// Test that calling |preventSilentAccess| from insecure context will not reach
// CredentialManagerImpl::PreventSilentAccess.
TEST_F(CredentialManagerTest, TryToPreventSilentAccessFromInsecureContext) {
// Inject JavaScript, set up WebState to have non-cryptographic scheme.
LoadHtml(@"<html></html>", GURL(kHttpWebOrigin));
LoadHtmlAndInject(@"<html></html>");
client_->set_current_url(GURL(kHttpWebOrigin));
// Manually store a PasswordForm in password store.
client_->password_store()->AddLogin(password_credential_form_1_);
// Call API method |preventSilentAccess|.
ExecuteJavaScript(
@"navigator.credentials.preventSilentAccess().catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof DOMException && "
" reason.name == DOMException.INVALID_STATE_ERR);"
" test_promise_rejected_ = true;"
"});");
// Wait for Promise to be rejected.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_rejected_") isEqual:@YES]);
});
// Check that Promise was rejected with InvalidStateError.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
// Check that credential in password store was not affected by the call.
WaitForBackgroundTasks();
TestPasswordStore::PasswordMap passwords =
client_->password_store()->stored_passwords();
EXPECT_EQ(1u, passwords.size());
ASSERT_EQ(1u, passwords[kHttpsWebOrigin].size());
autofill::PasswordForm form = passwords[kHttpsWebOrigin][0];
EXPECT_EQ(false, form.skip_zero_click);
}
// Tests that after |navigator.credentials.preventSilentAccess| is called, user
// will be prompted to choose credentials.
TEST_F(CredentialManagerTest, PreventSilentAccess) {
// Manually store two PasswordForms in password store.
password_credential_form_1_.skip_zero_click = false;
password_credential_form_2_.skip_zero_click = false;
client_->password_store()->AddLogin(password_credential_form_1_);
client_->password_store()->AddLogin(password_credential_form_2_);
// Call API method |preventSilentAccess|.
ExecuteJavaScript(
@"navigator.credentials.preventSilentAccess()"
".then(function(result) {"
" test_result_ = (result == undefined);"
" test_promise_resolved_ = true;"
"});");
// Wait for Promise to be resolved.
WaitForCondition(^{
return static_cast<bool>(
[ExecuteJavaScript(@"test_promise_resolved_") isEqual:@YES]);
});
// Check that Promise was resolved with |undefined|.
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_"));
// Check that |preventSilentAccess| set a |skip_zero_click| flag on stored
// credential.
WaitForBackgroundTasks();
TestPasswordStore::PasswordMap passwords =
client_->password_store()->stored_passwords();
std::vector<autofill::PasswordForm> forms = passwords[kHttpsWebOrigin];
ASSERT_EQ(2u, forms.size());
EXPECT_TRUE(forms[0].skip_zero_click);
EXPECT_TRUE(forms[1].skip_zero_click);
}
class WebStateContentIsSecureHtmlTest : public CredentialManagerBaseTest {};
// Tests that HTTPS websites with valid SSL certificate are recognized as
// secure.
TEST_F(WebStateContentIsSecureHtmlTest, AcceptHttpsUrls) {
LoadHtml(@"<html></html>", GURL(kHttpsWebOrigin));
UpdateSslStatus(net::CERT_STATUS_IS_EV, web::SECURITY_STYLE_AUTHENTICATED,
web::SSLStatus::NORMAL_CONTENT);
EXPECT_TRUE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that WebStateContentIsSecureHtml returns false for HTTP origin.
TEST_F(WebStateContentIsSecureHtmlTest, HttpIsNotSecureContext) {
LoadHtml(@"<html></html>", GURL(kHttpWebOrigin));
EXPECT_FALSE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that WebStateContentIsSecureHtml returns false for HTTPS origin with
// valid SSL certificate but mixed contents.
TEST_F(WebStateContentIsSecureHtmlTest, InsecureContent) {
LoadHtml(@"<html></html>", GURL(kHttpsWebOrigin));
UpdateSslStatus(net::CERT_STATUS_IS_EV, web::SECURITY_STYLE_AUTHENTICATED,
web::SSLStatus::DISPLAYED_INSECURE_CONTENT);
EXPECT_FALSE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that WebStateContentIsSecureHtml returns false for HTTPS origin with
// invalid SSL certificate.
TEST_F(WebStateContentIsSecureHtmlTest, InvalidSslCertificate) {
LoadHtml(@"<html></html>", GURL(kHttpsWebOrigin));
UpdateSslStatus(net::CERT_STATUS_INVALID, web::SECURITY_STYLE_UNAUTHENTICATED,
web::SSLStatus::NORMAL_CONTENT);
EXPECT_FALSE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that data:// URI scheme is not accepted as secure context.
TEST_F(WebStateContentIsSecureHtmlTest, DataUriSchemeIsNotSecureContext) {
LoadHtml(@"<html></html>", GURL(kDataUriSchemeOrigin));
EXPECT_FALSE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that localhost is accepted as secure context.
TEST_F(WebStateContentIsSecureHtmlTest, LocalhostIsSecureContext) {
LoadHtml(@"<html></html>", GURL(kLocalhostOrigin));
EXPECT_TRUE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that file origin is accepted as secure context.
TEST_F(WebStateContentIsSecureHtmlTest, FileIsSecureContext) {
LoadHtml(@"<html></html>", GURL(kFileOrigin));
EXPECT_TRUE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Tests that content must be HTML.
TEST_F(WebStateContentIsSecureHtmlTest, ContentMustBeHtml) {
// No HTML is loaded on purpose, so that web_state()->ContentIsHTML() will
// return false.
EXPECT_FALSE(password_manager::WebStateContentIsSecureHtml(web_state()));
}
// Copyright 2017 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 "ios/chrome/browser/passwords/js_credential_manager.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "ios/web/public/test/web_js_test.h"
#include "ios/web/public/test/web_test_with_web_state.h"
#include "url/origin.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
constexpr char kTestIconUrl[] = "https://www.google.com/favicon.ico";
constexpr char kTestWebOrigin[] = "https://example.com";
} // namespace
// Tests for js_credential_manager.mm file. Its functions
// RejectCredentialPromiseWith* and ResolveCredentialPromiseWith* are tested as
// follows: 1. |credential_manager| early script is injected to the page. 2. A
// Promise is created. Depending on a test, it's |resolve| or |reject|
// function is expected to be called. That function stores the result or error
// in variable(s) with test_* prefix.
// 3. To check if JavaScript executed by JsCredentialManager was correct, we
// check the values of test_* variable(s).
class JsCredentialManagerTest
: public web::WebJsTest<web::WebTestWithWebState> {
public:
JsCredentialManagerTest()
: web::WebJsTest<web::WebTestWithWebState>(@[ @"credential_manager" ]) {}
void SetUp() override {
WebTestWithWebState::SetUp();
// Load empty HTML and inject |credential_manager| early script.
LoadHtmlAndInject(@"<html></html>");
}
DISALLOW_COPY_AND_ASSIGN(JsCredentialManagerTest);
};
// Tests that ResolveCredentialPromiseWithCredentialInfo resolves the promise
// with JavaScript PasswordCredential object containing correct values.
TEST_F(JsCredentialManagerTest, ResolveWithPasswordCredential) {
// Let requestId be 3.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(3)."
"then(function(result) {"
" test_credential_ = result;"
"});");
password_manager::CredentialInfo credential;
credential.type = password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD;
credential.id = base::ASCIIToUTF16("test@google.com");
credential.name = base::ASCIIToUTF16("Test User");
credential.icon = GURL(base::ASCIIToUTF16(kTestIconUrl));
credential.password = base::ASCIIToUTF16("32njk \\4 s3cr3t \\n' 1");
ResolveCredentialPromiseWithCredentialInfo(web_state(), 3, credential);
// Wait for Promise to be resolved before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"object" isEqual:ExecuteJavaScript(@"typeof test_credential_")]);
});
EXPECT_NSEQ(@"password", ExecuteJavaScript(@"test_credential_.type"));
EXPECT_NSEQ(@"test@google.com", ExecuteJavaScript(@"test_credential_.id"));
EXPECT_NSEQ(@"Test User", ExecuteJavaScript(@"test_credential_.name"));
EXPECT_NSEQ(base::SysUTF16ToNSString(base::ASCIIToUTF16(kTestIconUrl)),
ExecuteJavaScript(@"test_credential_.iconURL"));
EXPECT_NSEQ(@"32njk \\4 s3cr3t \\n' 1",
ExecuteJavaScript(@"test_credential_.password"));
}
// Tests that ResolveCredentialPromiseWithCredentialInfo resolves the promise
// with JavaScript FederatedCredential object containing correct values.
TEST_F(JsCredentialManagerTest, ResolveWithFederatedCredential) {
// Let requestId be 3.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(3)."
"then(function(result) {"
" test_credential_ = result;"
"});");
password_manager::CredentialInfo credential;
credential.type = password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED;
credential.id = base::ASCIIToUTF16("test@google.com");
credential.name = base::ASCIIToUTF16("Test User");
credential.icon = GURL(base::ASCIIToUTF16(kTestIconUrl));
credential.federation = url::Origin::Create(GURL(kTestWebOrigin));
ResolveCredentialPromiseWithCredentialInfo(web_state(), 3, credential);
// Wait for Promise to be resolved before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"object" isEqual:ExecuteJavaScript(@"typeof test_credential_")]);
});
EXPECT_NSEQ(@"federated", ExecuteJavaScript(@"test_credential_.type"));
EXPECT_NSEQ(@"test@google.com", ExecuteJavaScript(@"test_credential_.id"));
EXPECT_NSEQ(@"Test User", ExecuteJavaScript(@"test_credential_.name"));
EXPECT_NSEQ(base::SysUTF16ToNSString(base::ASCIIToUTF16(kTestIconUrl)),
ExecuteJavaScript(@"test_credential_.iconURL"));
EXPECT_NSEQ(base::SysUTF16ToNSString(base::ASCIIToUTF16(kTestWebOrigin)),
ExecuteJavaScript(@"test_credential_.provider"));
}
// Tests that ResolveCredentialPromiseWithCredentialInfo resolves the promise
// with void when optional CredentialInfo is null.
TEST_F(JsCredentialManagerTest, ResolveWithNullCredential) {
// Let requestId be 3.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(3)."
"then(function(result) {"
" test_result_ = (result == undefined);"
"});");
base::Optional<password_manager::CredentialInfo> null_credential;
ResolveCredentialPromiseWithCredentialInfo(web_state(), 3, null_credential);
// Wait for Promise to be resolved before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"boolean" isEqual:ExecuteJavaScript(@"typeof test_result_")]);
});
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_"));
}
// Tests that ResolveCredentialPromiseWithUndefined resolves the promise with no
// value.
TEST_F(JsCredentialManagerTest, ResolveWithUndefined) {
// Let requestId be equal 5.
// Only when the promise is resolved with undefined, will the
// |test_result_| be true.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(5)."
"then(function(result) {"
" test_result_ = (result == undefined);"
"});");
ResolveCredentialPromiseWithUndefined(web_state(), 5);
// Wait for Promise to be resolved before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"boolean" isEqual:ExecuteJavaScript(@"typeof test_result_")]);
});
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_"));
}
// Tests that RejectCredentialPromiseWithTypeError rejects the promise with
// TypeError and correct message.
TEST_F(JsCredentialManagerTest, RejectWithTypeError) {
// Let requestId be equal 100.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(100)."
"catch(function(reason) {"
" test_result_valid_type_ = (reason instanceof TypeError);"
" test_result_message_ = reason.message;"
"});");
RejectCredentialPromiseWithTypeError(
web_state(), 100, base::ASCIIToUTF16("message with \"quotation\" marks"));
// Wait for Promise to be rejected before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"string" isEqual:ExecuteJavaScript(@"typeof test_result_message_")]);
});
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
EXPECT_NSEQ(@"message with \"quotation\" marks",
ExecuteJavaScript(@"test_result_message_"));
}
// Tests that RejectCredentialPromiseWithInvalidStateError rejects the promise
// with DOMException(message, INVALID_STATE_ERR), where |message| is correct
// message taken as argument.
TEST_F(JsCredentialManagerTest, RejectWithInvalidState) {
// Let requestId be 0.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(0)."
"catch(function(reason) {"
" test_result_valid_type_ ="
" (reason.name == DOMException.INVALID_STATE_ERR);"
" test_result_message_ = reason.message;"
"});");
RejectCredentialPromiseWithInvalidStateError(
web_state(), 0, base::ASCIIToUTF16("A 'get()' request is pending"));
// Wait for Promise to be rejected before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"string" isEqual:ExecuteJavaScript(@"typeof test_result_message_")]);
});
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
EXPECT_NSEQ(@"A 'get()' request is pending",
ExecuteJavaScript(@"test_result_message_"));
}
// Tests that RejectCredentialPromiseWithNotSupportedError rejects the promise
// with DOMException(message, NOT_SUPPORTED_ERR), where |message| is correct
// message taken as argument.
TEST_F(JsCredentialManagerTest, RejectWithNotSupportedError) {
// Let requestId be 0.
ExecuteJavaScript(
@"__gCrWeb.credentialManager.createPromise_(0)."
"catch(function(reason) {"
" test_result_valid_type_ ="
" (reason.name == DOMException.NOT_SUPPORTED_ERR);"
" test_result_message_ = reason.message;"
"});");
RejectCredentialPromiseWithNotSupportedError(
web_state(), 0,
base::ASCIIToUTF16(
"An error occured while talking to the credential manager."));
// Wait for Promise to be rejected before checking the values.
WaitForCondition(^{
return static_cast<bool>(
[@"string" isEqual:ExecuteJavaScript(@"typeof test_result_message_")]);
});
EXPECT_NSEQ(@YES, ExecuteJavaScript(@"test_result_valid_type_"));
EXPECT_NSEQ(@"An error occured while talking to the credential manager.",
ExecuteJavaScript(@"test_result_message_"));
}
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
#include "components/strings/grit/components_strings.h" #include "components/strings/grit/components_strings.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h" #include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/passwords/password_manager_features.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h" #import "ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_error.h" #import "ios/chrome/browser/safe_browsing/safe_browsing_error.h"
#import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h" #import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
...@@ -176,29 +175,6 @@ TEST_F(ChromeWebClientTest, WKWebViewEarlyPageScriptAutofillController) { ...@@ -176,29 +175,6 @@ TEST_F(ChromeWebClientTest, WKWebViewEarlyPageScriptAutofillController) {
web_view, @"typeof __gCrWeb.autofill")); web_view, @"typeof __gCrWeb.autofill"));
} }
// Tests that ChromeWebClient provides credential manager script for WKWebView
// if and only if the feature is enabled.
TEST_F(ChromeWebClientTest, WKWebViewEarlyPageScriptCredentialManager) {
// Chrome scripts rely on __gCrWeb object presence.
WKWebView* web_view = web::BuildWKWebView(CGRectZero, browser_state());
web::test::ExecuteJavaScript(web_view, @"__gCrWeb = {};");
web::ScopedTestingWebClient web_client(std::make_unique<ChromeWebClient>());
NSString* script =
web_client.Get()->GetDocumentStartScriptForMainFrame(browser_state());
web::test::ExecuteJavaScript(web_view, script);
EXPECT_NSEQ(@"undefined", web::test::ExecuteJavaScript(
web_view, @"typeof navigator.credentials"));
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kCredentialManager);
script =
web_client.Get()->GetDocumentStartScriptForMainFrame(browser_state());
web::test::ExecuteJavaScript(web_view, script);
EXPECT_NSEQ(@"object", web::test::ExecuteJavaScript(
web_view, @"typeof navigator.credentials"));
}
// Tests PrepareErrorPage wth non-post, not Off The Record error. // Tests PrepareErrorPage wth non-post, not Off The Record error.
TEST_F(ChromeWebClientTest, PrepareErrorPageNonPostNonOtr) { TEST_F(ChromeWebClientTest, PrepareErrorPageNonPostNonOtr) {
ChromeWebClient web_client; ChromeWebClient web_client;
......
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