| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/webid/idp_network_request_manager.h" |
| |
| #include <array> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/values.h" |
| #include "content/browser/webid/test/mock_permission_delegate.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/manifest_icon_downloader.h" |
| #include "content/public/browser/webid/identity_request_dialog_controller.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/base/network_isolation_partition.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" |
| #include "services/network/public/cpp/cors/cors_error_status.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/url_loader_completion_status.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/client_security_state.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h" |
| #include "third_party/re2/src/re2/re2.h" |
| #include "ui/gfx/image/image.h" |
| #include "url/gurl.h" |
| |
| using ::testing::_; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using IdpClientMetadata = content::IdpNetworkRequestManager::ClientMetadata; |
| using TokenResult = content::IdpNetworkRequestManager::TokenResult; |
| using Endpoints = content::IdpNetworkRequestManager::Endpoints; |
| using FetchStatus = content::IdpNetworkRequestManager::FetchStatus; |
| using ParseStatus = content::IdpNetworkRequestManager::ParseStatus; |
| using AccountsRequestCallback = |
| content::IdpNetworkRequestManager::AccountsRequestCallback; |
| using LoginState = content::IdentityRequestAccount::LoginState; |
| using AccountsResponseInvalidReason = |
| content::IdpNetworkRequestManager::AccountsResponseInvalidReason; |
| using ErrorDialogType = content::IdpNetworkRequestManager::FedCmErrorDialogType; |
| using ErrorUrlType = content::IdpNetworkRequestManager::FedCmErrorUrlType; |
| using TokenResponseType = |
| content::IdpNetworkRequestManager::FedCmTokenResponseType; |
| |
| namespace content { |
| |
| namespace { |
| |
| // Values for testing. Real minimum and ideal sizes are different. |
| constexpr int kTestBrandIconMinimumSize = 16; |
| constexpr int kTestBrandIconIdealSize = 32; |
| |
| constexpr char kTestIdpUrl[] = "https://idp.test"; |
| constexpr char kTestRpUrl[] = "https://rp.test"; |
| constexpr char kTestWellKnownUrl[] = |
| "https://idp.test/.well-known/web-identity"; |
| constexpr char kTestConfigUrl[] = "https://idp.test/fedcm.json"; |
| constexpr char kTestAccountsEndpoint[] = "https://idp.test/accounts_endpoint"; |
| constexpr char kTestTokenEndpoint[] = "https://idp.test/token_endpoint"; |
| constexpr char kTestClientMetadataEndpoint[] = |
| "https://idp.test/client_metadata_endpoint"; |
| constexpr char kTestDisconnectEndpoint[] = |
| "https://idp.test/revocation_endpoint"; |
| constexpr char kTestLocalHostTokenEndpoint[] = |
| "http://localhost/token_endpoint"; |
| |
| constexpr char kSingleAccountEndpointValidJson[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "given_name": "Ken", |
| "picture": "https://idp.test/profile/1" |
| } |
| ] |
| })"; |
| |
| // Replaces the first line with the passed-in JSON key in `input` with |
| // `new_line`. |
| std::string ReplaceFirstLineWithKeyFromJson(const std::string& key, |
| const std::string& new_line, |
| const std::string& input, |
| bool replace_all) { |
| std::string pattern = R"(^[ ]*")" + key + R"(".*$)"; |
| std::string result = input; |
| RE2::Options options; |
| options.set_posix_syntax(true); |
| options.set_one_line(false); |
| RE2 re2(pattern, options); |
| if (replace_all) |
| RE2::GlobalReplace(&result, re2, new_line); |
| else |
| RE2::Replace(&result, re2, new_line); |
| return result; |
| } |
| |
| // Removes all lines with the passed-in JSON key in `input`. |
| std::string RemoveAllLinesWithKeyFromJson(const std::string& key, |
| const std::string& input) { |
| return ReplaceFirstLineWithKeyFromJson(key, "", input, /*replace_all=*/true); |
| } |
| |
| url::Origin GetOriginHeader(const network::ResourceRequest& request) { |
| return url::Origin::Create( |
| GURL(request.headers.GetHeader(net::HttpRequestHeaders::kOrigin) |
| .value_or(std::string()))); |
| } |
| |
| class IdpNetworkRequestManagerTest : public ::testing::Test { |
| public: |
| std::unique_ptr<IdpNetworkRequestManager> CreateTestManager( |
| const char* top_level_origin = nullptr) { |
| test_permission_delegate_ = |
| std::make_unique<NiceMock<MockPermissionDelegate>>(); |
| url::Origin top_level_origin_obj; |
| if (top_level_origin) { |
| top_level_origin_obj = url::Origin::Create(GURL(top_level_origin)); |
| } |
| return std::make_unique<IdpNetworkRequestManager>( |
| url::Origin::Create(GURL(kTestRpUrl)), top_level_origin_obj, |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_), |
| test_permission_delegate_.get(), |
| network::mojom::ClientSecurityState::New(), content::FrameTreeNodeId()); |
| } |
| |
| void AddResponse(const GURL& url, |
| net::HttpStatusCode http_status, |
| const std::string& mime_type, |
| const std::string& content, |
| bool cors_error = false) { |
| auto head = network::mojom::URLResponseHead::New(); |
| std::string raw_header = "HTTP/1.1 " + base::NumberToString(http_status) + |
| " " + net::GetHttpReasonPhrase(http_status) + |
| "\n" |
| "Content-type: " + |
| mime_type + "\n\n"; |
| head->headers = net::HttpResponseHeaders::TryToCreate(raw_header); |
| test_url_loader_factory().AddResponse( |
| url, std::move(head), content, |
| cors_error |
| ? network::URLLoaderCompletionStatus(network::CorsErrorStatus( |
| network::mojom::CorsError::kMissingAllowOriginHeader)) |
| : network::URLLoaderCompletionStatus()); |
| } |
| |
| std::tuple<FetchStatus, IdpNetworkRequestManager::WellKnown> |
| SendWellKnownRequestAndWaitForResponse( |
| const char* test_data, |
| net::HttpStatusCode http_status = net::HTTP_OK, |
| const std::string& mime_type = "application/json") { |
| GURL well_known_url(kTestWellKnownUrl); |
| AddResponse(well_known_url, http_status, mime_type, test_data); |
| |
| base::RunLoop run_loop; |
| FetchStatus parsed_fetch_status; |
| IdpNetworkRequestManager::WellKnown parsed_wellknow; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus fetch_status, |
| const IdpNetworkRequestManager::WellKnown& well_known) { |
| parsed_fetch_status = fetch_status; |
| parsed_wellknow = well_known; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->FetchWellKnown(GURL(kTestIdpUrl), std::move(callback)); |
| run_loop.Run(); |
| |
| return {parsed_fetch_status, parsed_wellknow}; |
| } |
| |
| std::tuple<FetchStatus, IdentityProviderMetadata> |
| SendConfigRequestAndWaitForResponse( |
| const char* test_data, |
| net::HttpStatusCode http_status = net::HTTP_OK, |
| const std::string& mime_type = "application/json", |
| blink::mojom::RpMode rp_mode = blink::mojom::RpMode::kPassive) { |
| GURL config_url(kTestConfigUrl); |
| AddResponse(config_url, http_status, mime_type, test_data); |
| |
| base::RunLoop run_loop; |
| FetchStatus parsed_fetch_status; |
| IdentityProviderMetadata parsed_idp_metadata; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus fetch_status, Endpoints endpoints, |
| IdentityProviderMetadata idp_metadata) { |
| parsed_fetch_status = fetch_status; |
| parsed_idp_metadata = std::move(idp_metadata); |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->FetchConfig(GURL(kTestConfigUrl), rp_mode, kTestBrandIconIdealSize, |
| kTestBrandIconMinimumSize, std::move(callback)); |
| run_loop.Run(); |
| |
| return {parsed_fetch_status, parsed_idp_metadata}; |
| } |
| |
| std::tuple<FetchStatus, std::vector<IdentityRequestAccountPtr>> |
| SendAccountsRequestWithStoredAccounts(base::Value::List test_accounts, |
| const char* client_id = "") { |
| GURL accounts_endpoint(kTestAccountsEndpoint); |
| url::Origin idp_origin = url::Origin::Create(accounts_endpoint); |
| |
| base::RunLoop run_loop; |
| FetchStatus parsed_accounts_response; |
| std::vector<IdentityRequestAccountPtr> parsed_accounts; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus response, |
| std::vector<IdentityRequestAccountPtr> accounts) { |
| parsed_accounts_response = response; |
| parsed_accounts = accounts; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| |
| EXPECT_CALL(*test_permission_delegate_, GetAccounts(_)) |
| .WillOnce(Return(test_accounts.Clone())); |
| manager->SendAccountsRequest(idp_origin, GURL(), client_id, |
| std::move(callback)); |
| run_loop.Run(); |
| |
| return {parsed_accounts_response, parsed_accounts}; |
| } |
| |
| std::tuple<FetchStatus, std::vector<IdentityRequestAccountPtr>> |
| SendAccountsRequestAndWaitForResponse( |
| const std::string& test_accounts, |
| const char* client_id = "", |
| net::HttpStatusCode response_code = net::HTTP_OK, |
| const std::string& mime_type = "application/json") { |
| GURL accounts_endpoint(kTestAccountsEndpoint); |
| AddResponse(accounts_endpoint, response_code, mime_type, test_accounts); |
| |
| base::RunLoop run_loop; |
| FetchStatus parsed_accounts_response; |
| std::vector<IdentityRequestAccountPtr> parsed_accounts; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus response, |
| std::vector<IdentityRequestAccountPtr> accounts) { |
| parsed_accounts_response = response; |
| parsed_accounts = accounts; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendAccountsRequest(url::Origin::Create(accounts_endpoint), |
| accounts_endpoint, client_id, |
| std::move(callback)); |
| run_loop.Run(); |
| |
| return {parsed_accounts_response, parsed_accounts}; |
| } |
| |
| IdpNetworkRequestManager::RecordErrorMetricsCallback |
| CreateErrorMetricsCallback(base::RunLoop& run_loop) { |
| return base::BindLambdaForTesting( |
| [&](TokenResponseType token_response_type, |
| std::optional<ErrorDialogType> error_dialog_type, |
| std::optional<ErrorUrlType> error_url_type) { |
| token_response_type_ = token_response_type; |
| error_dialog_type_ = error_dialog_type; |
| error_url_type_ = error_url_type; |
| run_loop.Quit(); |
| }); |
| } |
| |
| std::tuple<FetchStatus, TokenResult> SendTokenRequestAndWaitForResponse( |
| const char* account, |
| const char* request, |
| net::HttpStatusCode http_status = net::HTTP_OK, |
| const std::string& mime_type = "application/json", |
| const char* response = R"({"token": "token"})", |
| bool idp_blindness = false, |
| const char* token_endpoint_str = kTestTokenEndpoint, |
| bool cors_error = false) { |
| GURL token_endpoint{token_endpoint_str}; |
| AddResponse(token_endpoint, http_status, mime_type, response, cors_error); |
| |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| base::RunLoop run_loop; |
| auto callback = |
| base::BindLambdaForTesting([&](FetchStatus status, TokenResult result) { |
| fetch_status = status; |
| token_result = result; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, account, request, idp_blindness, |
| std::move(callback), base::DoNothing(), |
| CreateErrorMetricsCallback(run_loop)); |
| run_loop.Run(); |
| return {fetch_status, token_result}; |
| } |
| |
| IdpClientMetadata SendClientMetadataRequestAndWaitForResponse( |
| const char* client_id, |
| const std::string& response = R"({})", |
| const char* top_level_origin = nullptr) { |
| GURL client_id_endpoint(kTestClientMetadataEndpoint); |
| std::string url_string = |
| client_id_endpoint.spec() + "?client_id=" + client_id; |
| if (top_level_origin) { |
| url_string += std::string("&top_frame_origin=") + top_level_origin; |
| } |
| AddResponse(GURL(url_string), net::HTTP_OK, "application/json", response); |
| |
| IdpClientMetadata data; |
| base::RunLoop run_loop; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus status, IdpClientMetadata metadata) { |
| data = metadata; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = |
| CreateTestManager(top_level_origin); |
| manager->FetchClientMetadata( |
| client_id_endpoint, client_id, kTestBrandIconIdealSize, |
| kTestBrandIconMinimumSize, std::move(callback)); |
| run_loop.Run(); |
| return data; |
| } |
| |
| network::TestURLLoaderFactory& test_url_loader_factory() { |
| return test_url_loader_factory_; |
| } |
| |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| TokenResponseType token_response_type() { return token_response_type_; } |
| std::optional<ErrorDialogType> error_dialog_type() { |
| return error_dialog_type_; |
| } |
| std::optional<ErrorUrlType> error_url_type() { return error_url_type_; } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_{ |
| content::BrowserTaskEnvironment::MainThreadType::UI}; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| data_decoder::test::InProcessDataDecoder in_process_data_decoder; |
| base::HistogramTester histogram_tester_; |
| TokenResponseType token_response_type_; |
| std::optional<ErrorDialogType> error_dialog_type_; |
| std::optional<ErrorUrlType> error_url_type_; |
| std::unique_ptr<NiceMock<MockPermissionDelegate>> test_permission_delegate_; |
| }; |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountEmpty) { |
| const auto* test_empty_account_json = R"({ |
| "accounts" : [] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_empty_account_json); |
| |
| EXPECT_EQ(ParseStatus::kEmptyListError, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountListIsEmpty, 1); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountSingle) { |
| const auto* test_single_account_json = kSingleAccountEndpointValidJson; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_single_account_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ(1UL, accounts.size()); |
| EXPECT_EQ("1234", accounts[0]->id); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountMultiple) { |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "given_name": "Ken", |
| "picture": "https://idp.test/profile/1" |
| }, |
| { |
| "id" : "5678", |
| "email": "sam@idp.test", |
| "name": "Sam G. Test", |
| "given_name": "Sam", |
| "picture": "https://idp.test/profile/2" |
| } |
| ] |
| })"; |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ(2UL, accounts.size()); |
| EXPECT_EQ("1234", accounts[0]->id); |
| EXPECT_EQ("5678", accounts[1]->id); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountOptionalFields) { |
| // given_name and picture fields are optional |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountRequiredFields) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmAlternativeIdentifiers); |
| |
| { |
| base::HistogramTester histogram_tester; |
| std::string test_account_missing_account_id_json = |
| RemoveAllLinesWithKeyFromJson("id", kSingleAccountEndpointValidJson); |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse( |
| test_account_missing_account_id_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester.ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountMissesRequiredField, 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| std::string test_account_missing_email_json = |
| RemoveAllLinesWithKeyFromJson("email", kSingleAccountEndpointValidJson); |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_account_missing_email_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester.ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountMissesRequiredField, 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| std::string test_account_missing_name_json = |
| RemoveAllLinesWithKeyFromJson("name", kSingleAccountEndpointValidJson); |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_account_missing_name_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester.ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountMissesRequiredField, 1); |
| } |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountRequiredFieldNonEmpty) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmAlternativeIdentifiers); |
| |
| { |
| base::HistogramTester histogram_tester; |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "test@email.example", |
| "name": " " |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester.ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountMissesRequiredField, 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "", |
| "name": "Test User" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester.ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountMissesRequiredField, 1); |
| } |
| } |
| |
| // Test that parsing accounts fails if two accounts have the same account id. |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountDuplicateIds) { |
| const auto* accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example" |
| }, |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester()->ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kAccountsShareSameId, 1); |
| |
| // Test that JSON is valid with exception of duplicate id. |
| std::string accounts_json_different_account_ids = |
| ReplaceFirstLineWithKeyFromJson("id", R"("id": "5678",)", accounts_json, |
| /*replace_all=*/false); |
| |
| std::tie(accounts_response, accounts) = SendAccountsRequestAndWaitForResponse( |
| accounts_json_different_account_ids); |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountPictureUrl) { |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "picture": "https://idp.test/profile/1234" |
| }, |
| { |
| "id" : "567", |
| "email": "sam@idp.test", |
| "name": "Sam R. Example", |
| "picture": "invalid_url" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts[0]->picture.is_valid()); |
| EXPECT_EQ(GURL("https://idp.test/profile/1234"), accounts[0]->picture); |
| EXPECT_FALSE(accounts[1]->picture.is_valid()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountUnicode) { |
| auto TestAccountWithKeyValue = [](const std::string& key, |
| const std::string& value) { |
| static constexpr char kJson[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "%s": "%s" |
| } |
| ] |
| })"; |
| return base::StringPrintf(kJson, key.c_str(), value.c_str()); |
| }; |
| |
| std::array<std::string, 3> test_values{"ascii", "🦖", "مجید"}; |
| |
| for (auto& test_value : test_values) { |
| const auto& accounts_json = TestAccountWithKeyValue("name", test_value); |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(accounts_json.c_str()); |
| |
| EXPECT_EQ(1UL, accounts.size()); |
| EXPECT_EQ(test_value, accounts[0]->display_name); |
| } |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountInvalid) { |
| const auto* test_invalid_account_json = "{}"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_invalid_account_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester()->ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kNoAccountsKey, 1); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountMalformed) { |
| const auto* test_invalid_account_json = "malformed_json"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_invalid_account_json); |
| |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| histogram_tester()->ExpectUniqueSample( |
| "Blink.FedCm.Status.AccountsResponseInvalidReason", |
| AccountsResponseInvalidReason::kResponseIsNotJsonOrDict, 1); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountLabelsOldSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // New syntax should be ignored with the flag disabled. |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "label_hints": ["x1", 42, "x2"], |
| "labels": ["l1", 42, "l2"] |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| // The integer in the second position should be ignored. |
| ASSERT_EQ(2u, accounts[0]->labels.size()); |
| EXPECT_EQ("l1", accounts[0]->labels[0]); |
| EXPECT_EQ("l2", accounts[0]->labels[1]); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountLabelsOldAndNewSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // label_hints should take precedence. |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "label_hints": ["l1", 42, "l2"], |
| "labels": ["x1", 42, "x2"] |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| // The integer in the second position should be ignored. |
| ASSERT_EQ(2u, accounts[0]->labels.size()); |
| EXPECT_EQ("l1", accounts[0]->labels[0]); |
| EXPECT_EQ("l2", accounts[0]->labels[1]); |
| } |
| |
| // TODO(crbug.com/404568028): Delete when |
| // kFedCmUseOtherAccountAndLabelsNewSyntax is removed. |
| TEST_F(IdpNetworkRequestManagerTest, DoNotParseAccountLabelsOldSyntaxWithFlag) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // With new syntax enabled, old syntax should be ignored. |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "labels": ["x1", 42, "x2"] |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| ASSERT_EQ(0u, accounts[0]->labels.size()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountLabelHints) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "label_hints": ["l1", 42, "l2"] |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| // The integer in the second position should be ignored. |
| ASSERT_EQ(2u, accounts[0]->labels.size()); |
| EXPECT_EQ("l1", accounts[0]->labels[0]); |
| EXPECT_EQ("l2", accounts[0]->labels[1]); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ComputeWellKnownUrl) { |
| EXPECT_EQ("https://localhost:8000/.well-known/web-identity", |
| IdpNetworkRequestManager::ComputeWellKnownUrl( |
| GURL("https://localhost:8000/test/")) |
| ->spec()); |
| |
| EXPECT_EQ("https://google.com/.well-known/web-identity", |
| IdpNetworkRequestManager::ComputeWellKnownUrl( |
| GURL("https://www.google.com:8000/test/")) |
| ->spec()); |
| |
| EXPECT_EQ(std::nullopt, IdpNetworkRequestManager::ComputeWellKnownUrl( |
| GURL("https://192.101.0.1/test/"))); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseUsername) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmAlternativeIdentifiers); |
| |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "email": "ken@idp.test", |
| "username": "ken" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| EXPECT_EQ("ken@idp.test", accounts[0]->email); |
| EXPECT_EQ("ken@idp.test", accounts[0]->display_identifier); |
| EXPECT_EQ("ken", accounts[0]->display_name); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParsePhoneNumber) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmAlternativeIdentifiers); |
| |
| const auto* test_accounts_json = R"({ |
| "accounts" : [ |
| { |
| "id": "1234", |
| "tel": "111-111-1111" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ("1234", accounts[0]->id); |
| EXPECT_EQ("", accounts[0]->email); |
| EXPECT_EQ("", accounts[0]->display_identifier); |
| EXPECT_EQ("111-111-1111", accounts[0]->display_name); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountSingleLightweightFedcm) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmLightweightMode); |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = SendAccountsRequestWithStoredAccounts( |
| base::Value::List().Append(base::Value::Dict() |
| .Set("id", "1234") |
| .Set("email", "ken@idp.test") |
| .Set("name", "Ken R. Example"))); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ(1UL, accounts.size()); |
| |
| EXPECT_EQ("1234", accounts[0]->id); |
| EXPECT_EQ("ken@idp.test", accounts[0]->email); |
| EXPECT_EQ("Ken R. Example", accounts[0]->name); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseAccountEmptyLightweightFedcm) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmLightweightMode); |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestWithStoredAccounts(base::Value::List()); |
| |
| EXPECT_EQ(ParseStatus::kEmptyListError, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_EQ(0UL, accounts.size()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, CacheProfilePictures) { |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| url::Origin idp_origin = url::Origin::Create(GURL(kTestIdpUrl)); |
| GURL picture_url("https://idp.cdn.test/profile/1234"); |
| |
| manager->CacheAccountPictures(idp_origin, {picture_url}); |
| EXPECT_EQ(1, test_url_loader_factory().NumPending()); |
| network::TestURLLoaderFactory::PendingRequest* pending_request = |
| test_url_loader_factory().GetPendingRequest(0); |
| ASSERT_TRUE(pending_request); |
| network::ResourceRequest resource_request = pending_request->request; |
| EXPECT_FALSE(resource_request.load_flags & net::LOAD_ONLY_FROM_CACHE); |
| net::IsolationInfo expected_isolation_info = net::IsolationInfo::Create( |
| net::IsolationInfo::RequestType::kOther, |
| /*top_frame_origin=*/idp_origin, |
| /*frame_origin=*/url::Origin::Create(GURL("https://idp.cdn.test/")), |
| net::SiteForCookies(), |
| /*nonce=*/std::nullopt, |
| net::NetworkIsolationPartition::kFedCmUncredentialedRequests); |
| EXPECT_TRUE(expected_isolation_info.IsEqualForTesting( |
| resource_request.trusted_params->isolation_info)); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, DownloadAndDecodeCachedImage) { |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| url::Origin idp_origin = url::Origin::Create(GURL(kTestIdpUrl)); |
| GURL picture_url("https://idp.cdn.test/profile/1234"); |
| |
| manager->DownloadAndDecodeCachedImage( |
| idp_origin, picture_url, base::DoNothingAs<void(const gfx::Image&)>()); |
| |
| EXPECT_EQ(1, test_url_loader_factory().NumPending()); |
| network::TestURLLoaderFactory::PendingRequest* pending_request = |
| test_url_loader_factory().GetPendingRequest(0); |
| ASSERT_TRUE(pending_request); |
| network::ResourceRequest resource_request = pending_request->request; |
| EXPECT_TRUE(resource_request.load_flags & net::LOAD_ONLY_FROM_CACHE); |
| net::IsolationInfo expected_isolation_info = net::IsolationInfo::Create( |
| net::IsolationInfo::RequestType::kOther, |
| /*top_frame_origin=*/idp_origin, |
| /*frame_origin=*/url::Origin::Create(GURL("https://idp.cdn.test/")), |
| net::SiteForCookies(), |
| /*nonce=*/std::nullopt, |
| net::NetworkIsolationPartition::kFedCmUncredentialedRequests); |
| EXPECT_TRUE(expected_isolation_info.IsEqualForTesting( |
| resource_request.trusted_params->isolation_info)); |
| } |
| // Test that IdpNetworkRequestManager::FetchWellKnown() fails when the |
| // identity provider domain is empty. |
| TEST_F(IdpNetworkRequestManagerTest, FetchWellKnownIllegalDomainFails) { |
| GURL illegal_idp_url("https://192.101.0.1/test/"); |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| std::unique_ptr<MockPermissionDelegate> test_permission_delegate_ = |
| std::make_unique<MockPermissionDelegate>(); |
| |
| auto network_manager = std::make_unique<IdpNetworkRequestManager>( |
| url::Origin::Create(GURL(kTestRpUrl)), |
| /*rp_embedding_origin=*/url::Origin(), |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| test_permission_delegate_.get(), |
| network::mojom::ClientSecurityState::New(), content::FrameTreeNodeId()); |
| |
| base::RunLoop run_loop; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus fetch_status, |
| const IdpNetworkRequestManager::WellKnown& well_known) { |
| EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status); |
| // We receive OK here because |
| // IdpNetworkRequestManager::ComputeWellKnownUrl() fails. |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| run_loop.Quit(); |
| }); |
| network_manager->FetchWellKnown(illegal_idp_url, std::move(callback)); |
| run_loop.Run(); |
| |
| // Well-known download should not have been attempted. |
| EXPECT_EQ(0, test_url_loader_factory.NumPending()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseWellKnown) { |
| FetchStatus fetch_status; |
| IdpNetworkRequestManager::WellKnown well_known; |
| |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["https://idp.test/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")}, |
| well_known.provider_urls); |
| |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["https://idp.test/path/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, |
| well_known.provider_urls); |
| |
| // Value not a list |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": "https://idp.test/fedcm.json" |
| })"); |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status); |
| |
| // Toplevel not a dictionary |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"( |
| ["https://idp.test/fedcm.json"] |
| )"); |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status); |
| |
| // Incorrect key |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "providers": ["https://idp.test/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status); |
| |
| // Array entry not a string |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": [1] |
| })"); |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status); |
| |
| // Relative URLs |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")}, |
| well_known.provider_urls); |
| |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/.well-known/fedcm.json")}, |
| well_known.provider_urls); |
| |
| // Empty well known list |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": [] |
| })"); |
| EXPECT_EQ(ParseStatus::kEmptyListError, fetch_status.parse_status); |
| |
| // well-known file having valid account endpoints, |
| // login url and provider_urls |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "accounts_endpoint": "/accounts.php", |
| "login_url": "/login", |
| "provider_urls": ["https://idp.test/path/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts); |
| EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, |
| well_known.provider_urls); |
| |
| // well-known file having empty provider_urls and |
| // valid account endpoints and login url |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "accounts_endpoint": "/accounts.php", |
| "login_url": "/login" |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts); |
| EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url); |
| EXPECT_TRUE(well_known.provider_urls.empty()); |
| |
| // well-known file having empty account endpoints and valid |
| // login url and provider_urls |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "accounts_endpoint": "", |
| "login_url": "/login", |
| "provider_urls": ["https://idp.test/path/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_TRUE(well_known.accounts.is_empty()); |
| EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, |
| well_known.provider_urls); |
| |
| // well-known file having empty login url and valid |
| // account endpoints and provider_urls |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "accounts_endpoint": "/accounts.php", |
| "login_url": "", |
| "provider_urls": ["https://idp.test/path/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts); |
| EXPECT_TRUE(well_known.login_url.is_empty()); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, |
| well_known.provider_urls); |
| |
| // well-known file having valid provider urls with empty |
| // login url and account endpoints |
| std::tie(fetch_status, well_known) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "accounts_endpoint": "", |
| "login_url": "", |
| "provider_urls": ["https://idp.test/path/fedcm.json"] |
| })"); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_TRUE(well_known.accounts.is_empty()); |
| EXPECT_TRUE(well_known.login_url.is_empty()); |
| EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")}, |
| well_known.provider_urls); |
| } |
| |
| // Test that the "alpha" value in the "branding" JSON is ignored. |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingRemoveAlpha) { |
| const char test_json[] = R"({ |
| "branding" : { |
| "background_color": "#20202020" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(SkColorSetARGB(0xff, 0x20, 0x20, 0x20), |
| idp_metadata.brand_background_color); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingInvalidColor) { |
| const char test_json[] = R"({ |
| "branding" : { |
| "background_color": "fake_color" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(std::nullopt, idp_metadata.brand_background_color); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigWithInsufficientContrastTextColor) { |
| const char test_json[] = R"({ |
| "branding" : { |
| "background_color": "#000000", |
| "color": "#010101" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 0), idp_metadata.brand_background_color); |
| EXPECT_EQ(SkColorSetRGB(1, 1, 1), idp_metadata.brand_text_color); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigBrandingWithTextColorAndNoBackgroundColor) { |
| const char test_json[] = R"({ |
| "branding" : { |
| "color": "blue" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(std::nullopt, idp_metadata.brand_background_color); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 255), idp_metadata.brand_text_color); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingSelectBestSize) { |
| const char test_json[] = R"({ |
| "branding" : { |
| "icons": [ |
| { |
| "url": "https://example.com/10.png", |
| "size": 10 |
| }, |
| { |
| "url": "https://example.com/16.png", |
| "size": 16 |
| }, |
| { |
| "url": "https://example.com/31.png", |
| "size": 31 |
| }, |
| { |
| "url": "https://example.com/32.png", |
| "size": 32 |
| }, |
| { |
| "url": "https://example.com/33.png", |
| "size": 33 |
| } |
| ] |
| } |
| })"; |
| |
| ASSERT_EQ(32, kTestBrandIconIdealSize); |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("https://example.com/32.png", idp_metadata.brand_icon_url.spec()); |
| } |
| |
| // Test that the icon is rejected if there is an explicit brand icon size in the |
| // config and it is smaller than the `idp_brand_icon_minimum_size` parameter |
| // passed to IdpNetworkRequestManager::FetchConfig(). |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingMinSize) { |
| ASSERT_EQ(16, kTestBrandIconMinimumSize); |
| |
| { |
| const char test_json[] = R"({ |
| "branding" : { |
| "icons": [ |
| { |
| "url": "https://example.com/15.png", |
| "size": 15 |
| } |
| ] |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(GURL(), idp_metadata.brand_icon_url); |
| } |
| |
| { |
| const char test_json[] = R"({ |
| "branding" : { |
| "icons": [ |
| { |
| "url": "https://example.com/16.png", |
| "size": 16 |
| } |
| ] |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("https://example.com/16.png", idp_metadata.brand_icon_url.spec()); |
| } |
| } |
| |
| // Tests various scenarios on resolving branding icon's url for given config |
| // url. |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingIconReltivePath) { |
| // branding icon url domain matches with config url domain |
| { |
| const char test_json[] = R"({ |
| "branding" : { |
| "icons": [ |
| { |
| "url": "/16.png", |
| "size": 16 |
| } |
| ] |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("https://idp.test/16.png", idp_metadata.brand_icon_url); |
| } |
| |
| // branding icon url domain doesnt match with config url domain |
| { |
| const char test_json[] = R"({ |
| "branding" : { |
| "icons": [ |
| { |
| "url": "https://example.com/16.png", |
| "size": 16 |
| } |
| ] |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("https://example.com/16.png", idp_metadata.brand_icon_url); |
| } |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigSupportsOtherAccountActiveMode) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| const char test_json[] = R"({ |
| "modes": { |
| "active": { |
| "supports_use_other_account": true |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kActive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(true, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigSupportsOtherAccountPassiveMode) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // The toplevel field should be ignored with the flag disabled. |
| const char test_json[] = R"({ |
| "supports_use_other_account": false, |
| "modes": { |
| "passive": { |
| "supports_use_other_account": true |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kPassive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(true, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigSupportsOtherAccountOldAndNewSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // The toplevel field should take precedence. |
| const char test_json[] = R"({ |
| "supports_use_other_account": true, |
| "modes": { |
| "passive": { |
| "supports_use_other_account": false |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kPassive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(true, idp_metadata.supports_add_account); |
| } |
| |
| // TODO(crbug.com/404568028): Delete when |
| // kFedCmUseOtherAccountAndLabelsNewSyntax is removed. |
| TEST_F(IdpNetworkRequestManagerTest, |
| DoNotParseConfigSupportsOtherAccountOldSyntaxWithFlag) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // Old syntax should be ignored if new syntax is enabled. |
| const char test_json[] = R"({ |
| "modes": { |
| "passive": { |
| "supports_use_other_account": true |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kPassive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(false, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigSupportsOtherAccountNewSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| const char test_json[] = R"({ |
| "supports_use_other_account": true |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kPassive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(true, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigSupportsOtherAccountDifferentMode) { |
| const char test_json[] = R"({ |
| "modes": { |
| "active": { |
| "supports_use_other_account": true |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kPassive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(false, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigSupportsOtherAccountBothModes) { |
| const char test_json[] = R"({ |
| "modes": { |
| "active": { |
| "supports_use_other_account": false |
| }, |
| "passive": { |
| "supports_use_other_account": true |
| } |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| test_json, net::HTTP_OK, "application/json", |
| blink::mojom::RpMode::kActive); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(false, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| ParseConfigSupportsUseOtherAccountMissing) { |
| const char test_json[] = R"({ |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(false, idp_metadata.supports_add_account); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigRequestedLabelOldSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndDisableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // New syntax should be ignored with flag disabled. |
| const char test_json[] = R"({ |
| "account_label": "l1", |
| "accounts": { |
| "include": "l1" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("l1", idp_metadata.requested_label); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigRequestedLabelOldAndNewSyntax) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // New syntax should take precedence over old syntax. |
| const char test_json[] = R"({ |
| "account_label": "l1", |
| "accounts": { |
| "include": "l5" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("l1", idp_metadata.requested_label); |
| } |
| |
| // TODO(crbug.com/404568028): Delete when |
| // kFedCmUseOtherAccountAndLabelsNewSyntax is removed. |
| TEST_F(IdpNetworkRequestManagerTest, |
| DoNotParseConfigRequestedLabelOldSyntaxWithFlag) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| // Old syntax should be ignored if new syntax is enabled. |
| const char test_json[] = R"({ |
| "accounts": { |
| "include": "l5" |
| } |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("", idp_metadata.requested_label); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ParseConfigRequestedLabel) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmUseOtherAccountAndLabelsNewSyntax); |
| |
| const char test_json[] = R"({ |
| "account_label": "l1" |
| })"; |
| |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(test_json); |
| |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ("l1", idp_metadata.requested_label); |
| } |
| |
| // Tests that we send the correct origin for account requests. |
| TEST_F(IdpNetworkRequestManagerTest, AccountRequestOrigin) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url); |
| EXPECT_EQ(request.request_body, nullptr); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| EXPECT_FALSE( |
| request.headers.HasHeader(net::HttpRequestHeaders::kOrigin)); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| |
| const char test_accounts_json[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1234", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json); |
| |
| ASSERT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| } |
| |
| // Verifies that we correctly check the signed-in status. |
| TEST_F(IdpNetworkRequestManagerTest, AccountSignedInStatus) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url); |
| EXPECT_EQ(request.request_body, nullptr); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| EXPECT_FALSE( |
| request.headers.HasHeader(net::HttpRequestHeaders::kOrigin)); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| |
| const char test_accounts_json[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "approved_clients": ["xxx"] |
| }, |
| { |
| "id" : "2", |
| "email": "jim@idp.test", |
| "name": "Jim R. Example", |
| "approved_clients": [] |
| }, |
| { |
| "id" : "3", |
| "email": "rashida@idp.test", |
| "name": "Rashida R. Example", |
| "approved_clients": ["yyy"] |
| }, |
| { |
| "id" : "4", |
| "email": "wei@idp.test", |
| "name": "Wei R. Example" |
| }, |
| { |
| "id" : "5", |
| "email": "hans@idp.test", |
| "name": "Hans R. Example", |
| "approved_clients": ["xxx", "yyy"] |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json, "xxx"); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| ASSERT_EQ(5ul, accounts.size()); |
| ASSERT_TRUE(accounts[0]->idp_claimed_login_state.has_value()); |
| EXPECT_EQ(LoginState::kSignIn, *accounts[0]->idp_claimed_login_state); |
| ASSERT_TRUE(accounts[1]->idp_claimed_login_state.has_value()); |
| EXPECT_EQ(LoginState::kSignUp, *accounts[1]->idp_claimed_login_state); |
| ASSERT_TRUE(accounts[2]->idp_claimed_login_state.has_value()); |
| EXPECT_EQ(LoginState::kSignUp, *accounts[2]->idp_claimed_login_state); |
| EXPECT_FALSE(accounts[3]->idp_claimed_login_state.has_value()); |
| ASSERT_TRUE(accounts[4]->idp_claimed_login_state.has_value()); |
| EXPECT_EQ(LoginState::kSignIn, *accounts[4]->idp_claimed_login_state); |
| } |
| |
| // Tests the token request implementation. |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequest) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestTokenEndpoint), request.url); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| EXPECT_EQ(url::Origin::Create(GURL(kTestRpUrl)), |
| GetOriginHeader(request)); |
| |
| // Check that the request body is correct (should be "request") |
| ASSERT_NE(request.request_body, nullptr); |
| ASSERT_EQ(1ul, request.request_body->elements()->size()); |
| const network::DataElement& elem = |
| request.request_body->elements()->at(0); |
| ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type()); |
| const network::DataElementBytes& byte_elem = |
| elem.As<network::DataElementBytes>(); |
| EXPECT_EQ("request", byte_elem.AsStringPiece()); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = |
| SendTokenRequestAndWaitForResponse("account", "request"); |
| ASSERT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| ASSERT_EQ("token", token_result.token); |
| ASSERT_EQ(false, fetch_status.cors_error); |
| } |
| |
| // Tests the ID assertion request implementation when CORS is enforced on the |
| // endpoint. |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestWithCORS) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestTokenEndpoint), request.url); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| url::Origin rpOrigin = url::Origin::Create(GURL(kTestRpUrl)); |
| EXPECT_EQ(GetOriginHeader(request), rpOrigin); |
| |
| // Check that the request body is correct (should be "request") |
| ASSERT_NE(request.request_body, nullptr); |
| ASSERT_EQ(1ul, request.request_body->elements()->size()); |
| const network::DataElement& elem = |
| request.request_body->elements()->at(0); |
| ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type()); |
| const network::DataElementBytes& byte_elem = |
| elem.As<network::DataElementBytes>(); |
| EXPECT_EQ("request", byte_elem.AsStringPiece()); |
| ASSERT_EQ(request.mode, network::mojom::RequestMode::kCors); |
| ASSERT_EQ(request.request_initiator, rpOrigin); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = |
| SendTokenRequestAndWaitForResponse("account", "request"); |
| ASSERT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| ASSERT_EQ("token", token_result.token); |
| ASSERT_EQ(false, fetch_status.cors_error); |
| } |
| |
| // Tests the ID assertion request implementation when CORS is enforced on the |
| // endpoint and server responds with CORS Error |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestWithCORSError) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_FORBIDDEN, "application/json", |
| R"({"token": ""})", false, kTestTokenEndpoint, true); |
| |
| EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status); |
| EXPECT_EQ(net::ERR_FAILED, fetch_status.response_code); |
| ASSERT_EQ("", token_result.token); |
| ASSERT_EQ(true, fetch_status.cors_error); |
| } |
| |
| // Tests the client metadata implementation. |
| TEST_F(IdpNetworkRequestManagerTest, ClientMetadata) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| std::string url_string = |
| std::string(kTestClientMetadataEndpoint) + "?client_id=xxx"; |
| EXPECT_EQ(GURL(url_string), request.url); |
| EXPECT_EQ(request.request_body, nullptr); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| EXPECT_EQ(url::Origin::Create(GURL(kTestRpUrl)), |
| GetOriginHeader(request)); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse("xxx"); |
| ASSERT_TRUE(called); |
| ASSERT_EQ(GURL(), data.privacy_policy_url); |
| ASSERT_EQ(GURL(), data.terms_of_service_url); |
| ASSERT_EQ(GURL(), data.brand_icon_url); |
| ASSERT_FALSE(data.client_matches_top_frame_origin.has_value()); |
| } |
| |
| // Tests the "matches top frame" boolean. |
| TEST_F(IdpNetworkRequestManagerTest, ClientMatchesTopFrameOrigin) { |
| base::test::ScopedFeatureList list; |
| list.InitAndEnableFeature(features::kFedCmIframeOrigin); |
| |
| IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse( |
| "clientid", R"({"client_matches_top_frame_origin": false})", |
| "https://toplevel.example"); |
| ASSERT_TRUE(data.client_matches_top_frame_origin.has_value()); |
| EXPECT_FALSE(*data.client_matches_top_frame_origin); |
| } |
| |
| // Tests that we correctly records metrics regarding approved_clients. |
| TEST_F(IdpNetworkRequestManagerTest, RecordApprovedClientsMetrics) { |
| base::HistogramTester histogram_tester; |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url); |
| EXPECT_EQ(request.request_body, nullptr); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| EXPECT_FALSE( |
| request.headers.HasHeader(net::HttpRequestHeaders::kOrigin)); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| |
| const char test_accounts_json[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "approved_clients": [] |
| }, |
| { |
| "id" : "2", |
| "email": "jim@idp.test", |
| "name": "Jim R. Example", |
| "approved_clients": ["xxx"] |
| }, |
| { |
| "id" : "3", |
| "email": "rashida@idp.test", |
| "name": "Rashida R. Example", |
| "approved_clients": ["xxx", "yyy"] |
| }, |
| { |
| "id" : "4", |
| "email": "wei@idp.test", |
| "name": "Wei R. Example" |
| } |
| ] |
| })"; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = |
| SendAccountsRequestAndWaitForResponse(test_accounts_json, "xxx"); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| ASSERT_EQ(4ul, accounts.size()); |
| |
| histogram_tester.ExpectTotalCount("Blink.FedCm.ApprovedClientsExistence", 4); |
| histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsExistence", 1, |
| 3); |
| histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsExistence", 0, |
| 1); |
| |
| histogram_tester.ExpectTotalCount("Blink.FedCm.ApprovedClientsSize", 3); |
| histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 0, 1); |
| histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 1, 1); |
| histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 2, 1); |
| } |
| |
| // Test that the callback is not called after IdpNetworkRequestManager is |
| // destroyed. |
| TEST_F(IdpNetworkRequestManagerTest, DontCallCallbackAfterManagerDeletion) { |
| const char test_accounts_json[] = R"({ |
| "accounts" : [ |
| { |
| "id" : "1", |
| "email": "ken@idp.test", |
| "name": "Ken R. Example", |
| "approved_clients": [] |
| } |
| ] |
| })"; |
| |
| GURL accounts_endpoint(kTestAccountsEndpoint); |
| AddResponse(accounts_endpoint, net::HTTP_OK, "application/json", |
| test_accounts_json); |
| |
| bool callback_called = false; |
| auto callback = base::BindLambdaForTesting( |
| [&callback_called](FetchStatus response, |
| std::vector<IdentityRequestAccountPtr> accounts) { |
| callback_called = true; |
| }); |
| |
| { |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendAccountsRequest(url::Origin::Create(accounts_endpoint), |
| accounts_endpoint, /*client_id=*/"", |
| std::move(callback)); |
| // Destroy `manager`. |
| } |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(callback_called); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingWellKnown) { |
| FetchStatus fetch_status; |
| IdpNetworkRequestManager::WellKnown wellknown; |
| std::tie(fetch_status, wellknown) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["https://idp.test/fedcm.json"] |
| })", |
| net::HTTP_REQUEST_TIMEOUT); |
| EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_REQUEST_TIMEOUT, fetch_status.response_code); |
| EXPECT_EQ(std::set<GURL>{}, wellknown.provider_urls); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingConfig) { |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = |
| SendConfigRequestAndWaitForResponse(R"({ |
| "branding" : { |
| "color": "blue" |
| } |
| })", |
| net::HTTP_NOT_FOUND); |
| EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_NOT_FOUND, fetch_status.response_code); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingAccounts) { |
| FetchStatus fetch_status; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(fetch_status, accounts) = |
| SendAccountsRequestAndWaitForResponse(R"({ |
| "accounts" : [] |
| })", |
| "", net::HTTP_BAD_REQUEST); |
| EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_BAD_REQUEST, fetch_status.response_code); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, FetchClientMetadataValidUrls) { |
| // Both HTTPS and HTTP URLs are allowed. |
| { |
| const std::string privacy_policy_url = "https://privacy.policy"; |
| const std::string terms_of_service_url = "http://terms.of.service"; |
| const std::string brand_icon_url = "http://rp.brand.icon"; |
| |
| IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse( |
| /*client_id=*/"123", R"({"privacy_policy_url": ")" + |
| privacy_policy_url + |
| R"(", "terms_of_service_url": ")" + |
| terms_of_service_url + |
| R"(", "icons": [ |
| { |
| "url": ")" + brand_icon_url + |
| R"(", |
| "size": 40 |
| } |
| ]})"); |
| ASSERT_EQ(GURL(privacy_policy_url), data.privacy_policy_url); |
| ASSERT_EQ(GURL(terms_of_service_url), data.terms_of_service_url); |
| ASSERT_EQ(GURL(brand_icon_url), data.brand_icon_url); |
| } |
| |
| // local host URL is allowed. |
| { |
| const std::string privacy_policy_url = "http://localhost"; |
| const std::string terms_of_service_url = "http://127.0.0.1"; |
| const std::string brand_icon_url = "http://localhost"; |
| |
| IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse( |
| /*client_id=*/"123", R"({"privacy_policy_url": ")" + |
| privacy_policy_url + |
| R"(", "terms_of_service_url": ")" + |
| terms_of_service_url + |
| R"(", "icons": [ |
| { |
| "url": ")" + brand_icon_url + |
| R"(", |
| "size": 40 |
| } |
| ]})"); |
| ASSERT_EQ(GURL(privacy_policy_url), data.privacy_policy_url); |
| ASSERT_EQ(GURL(terms_of_service_url), data.terms_of_service_url); |
| ASSERT_EQ(GURL(brand_icon_url), data.brand_icon_url); |
| } |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, FetchClientMetadataInvalidUrls) { |
| // Non-HTTP(S) URLs should not be allowed. |
| const std::string privacy_policy_url = "chrome://settings"; |
| const std::string terms_of_service_url = "file:///Users/you/file.html"; |
| const std::string brand_icon_url = "about:blank"; |
| |
| IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse( |
| /*client_id=*/"123", R"({"privacy_policy_url": ")" + privacy_policy_url + |
| R"(", "terms_of_service_url": ")" + |
| terms_of_service_url + |
| R"(", "icons": [ |
| { |
| "url": ")" + brand_icon_url + |
| R"(", |
| "size": 40 |
| } |
| ]})"); |
| ASSERT_EQ(GURL(), data.privacy_policy_url); |
| ASSERT_EQ(GURL(), data.terms_of_service_url); |
| ASSERT_EQ(GURL(), data.brand_icon_url); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, WellKnownWrongMimeType) { |
| FetchStatus fetch_status; |
| IdpNetworkRequestManager::WellKnown wellknown; |
| std::tie(fetch_status, wellknown) = |
| SendWellKnownRequestAndWaitForResponse(R"({ |
| "provider_urls": ["https://idp.test/fedcm.json"] |
| })", |
| net::HTTP_OK, "text/html"); |
| EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| EXPECT_EQ(std::set<GURL>{}, wellknown.provider_urls); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ConfigWrongMimeType) { |
| FetchStatus fetch_status; |
| IdentityProviderMetadata idp_metadata; |
| std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse( |
| R"({"branding" : { "color": "blue" } })", net::HTTP_OK, "text/html"); |
| EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, AccountsWrongMimeType) { |
| const auto* test_single_account_json = kSingleAccountEndpointValidJson; |
| |
| FetchStatus accounts_response; |
| std::vector<IdentityRequestAccountPtr> accounts; |
| std::tie(accounts_response, accounts) = SendAccountsRequestAndWaitForResponse( |
| test_single_account_json, /*client_id=*/"", net::HTTP_OK, "text/html"); |
| |
| EXPECT_EQ(ParseStatus::kInvalidContentTypeError, |
| accounts_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, accounts_response.response_code); |
| EXPECT_TRUE(accounts.empty()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionWrongMimeType) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "text/html"); |
| EXPECT_EQ("", token_result.token); |
| EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status); |
| EXPECT_EQ(net::HTTP_OK, fetch_status.response_code); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, FetchingTokenLeadsToAContinuationUrl) { |
| net::HttpStatusCode http_status = net::HTTP_OK; |
| const std::string& mime_type = "application/json"; |
| |
| const char response[] = |
| R"({"continue_on": "https://idp.test/an-absolute-url-for-continuation"})"; |
| GURL token_endpoint(kTestTokenEndpoint); |
| AddResponse(token_endpoint, http_status, mime_type, response); |
| |
| base::RunLoop run_loop; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus status, TokenResult result) {}); |
| |
| auto on_continue = base::BindLambdaForTesting([&](FetchStatus status, |
| const GURL& url) { |
| // Checks that we got a continuation url event back. |
| EXPECT_EQ("https://idp.test/an-absolute-url-for-continuation", url.spec()); |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, "account", "request", false, |
| std::move(callback), std::move(on_continue), |
| CreateErrorMetricsCallback(run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorNotReceivedAndContinueOnReceived, |
| token_response_type()); |
| } |
| |
| //+ kTokenReceivedAndErrorReceivedAndContinueOnReceived = 5, |
| |
| TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithToken) { |
| net::HttpStatusCode http_status = net::HTTP_OK; |
| const std::string& mime_type = "application/json"; |
| |
| const char response[] = R"({"continue_on": "/", "token": "a_token"})"; |
| GURL token_endpoint(kTestTokenEndpoint); |
| AddResponse(token_endpoint, http_status, mime_type, response); |
| |
| base::RunLoop run_loop; |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, "account", "request", false, |
| base::DoNothing(), base::DoNothing(), |
| CreateErrorMetricsCallback(run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ( |
| TokenResponseType::kTokenReceivedAndErrorNotReceivedAndContinueOnReceived, |
| token_response_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithErrorAndToken) { |
| net::HttpStatusCode http_status = net::HTTP_OK; |
| const std::string& mime_type = "application/json"; |
| |
| const char response[] = |
| R"({"continue_on": "/", "token": "a_token", "error": {"code": "foo"}})"; |
| GURL token_endpoint(kTestTokenEndpoint); |
| AddResponse(token_endpoint, http_status, mime_type, response); |
| |
| base::RunLoop run_loop; |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, "account", "request", false, |
| base::DoNothing(), base::DoNothing(), |
| CreateErrorMetricsCallback(run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ( |
| TokenResponseType::kTokenReceivedAndErrorReceivedAndContinueOnReceived, |
| token_response_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithError) { |
| net::HttpStatusCode http_status = net::HTTP_OK; |
| const std::string& mime_type = "application/json"; |
| |
| const char response[] = R"({"continue_on": "/", "error": {"code": "foo"}})"; |
| GURL token_endpoint(kTestTokenEndpoint); |
| AddResponse(token_endpoint, http_status, mime_type, response); |
| |
| base::RunLoop run_loop; |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, "account", "request", false, |
| base::DoNothing(), base::DoNothing(), |
| CreateErrorMetricsCallback(run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ( |
| TokenResponseType::kTokenNotReceivedAndErrorReceivedAndContinueOnReceived, |
| token_response_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, ContinueOnCanBeRelativeUrl) { |
| net::HttpStatusCode http_status = net::HTTP_OK; |
| const std::string& mime_type = "application/json"; |
| |
| const char response[] = |
| R"({"continue_on": "/a-relative-url-for-continuation"})"; |
| GURL token_endpoint(kTestTokenEndpoint); |
| AddResponse(token_endpoint, http_status, mime_type, response); |
| |
| base::RunLoop run_loop; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus status, TokenResult result) {}); |
| |
| auto on_continue = base::BindLambdaForTesting([&](FetchStatus status, |
| const GURL& url) { |
| // Checks that we got a continuation url event back. |
| EXPECT_EQ("https://idp.test/a-relative-url-for-continuation", url.spec()); |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendTokenRequest(token_endpoint, "account", "request", false, |
| std::move(callback), std::move(on_continue), |
| base::DoNothing()); |
| run_loop.Run(); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithProperField) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "https://idp.test/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ("https://idp.test/error", token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type()); |
| EXPECT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithRelativePath) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ("https://idp.test/error", token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type()); |
| EXPECT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithCrossSiteUrl) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "https://cross-site-idp.test/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type()); |
| EXPECT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kCrossSite, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| IdAssertionRequestErrorWithSameSiteCrossOriginUrl) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "https://cross-origin.idp.test/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ("https://cross-origin.idp.test/error", token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type()); |
| EXPECT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kCrossOriginSameSite, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, |
| IdAssertionRequestErrorWithUntrustworthyUrl) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "http://idp.test/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type()); |
| ASSERT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kCrossSite, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithEmptyUrl) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type()); |
| EXPECT_FALSE(error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithLocalHostUrl) { |
| // allow localhost for error url |
| { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "url": "http://localhost/error" |
| } |
| })", |
| false, kTestLocalHostTokenEndpoint); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("", token_result.error->code); |
| EXPECT_EQ(GURL("http://localhost/error"), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kGenericEmptyWithUrl, *error_dialog_type()); |
| ASSERT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type()); |
| } |
| |
| { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({ |
| "error": { |
| "code": "invalid_request", |
| "url": "http://localhost/error" |
| } |
| })", |
| false, kTestLocalHostTokenEndpoint); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("invalid_request", token_result.error->code); |
| EXPECT_EQ(GURL("http://localhost/error"), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type()); |
| ASSERT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type()); |
| } |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse200NonParsable) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_OK, "application/json", R"({}})"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kGenericEmptyWithoutUrl, *error_dialog_type()); |
| EXPECT_FALSE(error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse500NonParsable) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_INTERNAL_SERVER_ERROR, "application/json", |
| R"({}})"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("server_error", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kServerErrorWithoutUrl, *error_dialog_type()); |
| EXPECT_FALSE(error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse503NonParsable) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_SERVICE_UNAVAILABLE, "application/json", |
| R"({}})"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("temporarily_unavailable", token_result.error->code); |
| EXPECT_EQ(GURL(), token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kTemporarilyUnavailableWithoutUrl, |
| *error_dialog_type()); |
| EXPECT_FALSE(error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponseWithErrorAndHttpError) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_SERVICE_UNAVAILABLE, "application/json", |
| R"({ |
| "error": { |
| "code": "temporarily_unavailable", |
| "url": "https://idp.test/error" |
| } |
| })"); |
| |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("temporarily_unavailable", token_result.error->code); |
| EXPECT_EQ("https://idp.test/error", token_result.error->url); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kTemporarilyUnavailableWithUrl, |
| *error_dialog_type()); |
| EXPECT_TRUE(error_url_type()); |
| EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponseWithTokenAndHttpError) { |
| FetchStatus fetch_status; |
| TokenResult token_result; |
| |
| std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse( |
| "account", "request", net::HTTP_FORBIDDEN); |
| |
| EXPECT_EQ("", token_result.token); |
| EXPECT_TRUE(token_result.error); |
| EXPECT_EQ("", token_result.error->code); |
| EXPECT_EQ("", token_result.error->url); |
| EXPECT_EQ(net::HTTP_FORBIDDEN, fetch_status.response_code); |
| EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status); |
| EXPECT_EQ(TokenResponseType:: |
| kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived, |
| token_response_type()); |
| EXPECT_TRUE(error_dialog_type()); |
| EXPECT_EQ(ErrorDialogType::kGenericEmptyWithoutUrl, *error_dialog_type()); |
| EXPECT_FALSE(error_url_type()); |
| } |
| |
| TEST_F(IdpNetworkRequestManagerTest, DisconnectRequest) { |
| bool called = false; |
| auto interceptor = |
| base::BindLambdaForTesting([&](const network::ResourceRequest& request) { |
| called = true; |
| EXPECT_EQ(GURL(kTestDisconnectEndpoint), request.url); |
| EXPECT_FALSE(request.referrer.is_valid()); |
| url::Origin rpOrigin = url::Origin::Create(GURL(kTestRpUrl)); |
| EXPECT_EQ(GetOriginHeader(request), rpOrigin); |
| |
| // Check that the request body is correct. |
| ASSERT_NE(request.request_body, nullptr); |
| ASSERT_EQ(1ul, request.request_body->elements()->size()); |
| const network::DataElement& elem = |
| request.request_body->elements()->at(0); |
| ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type()); |
| const network::DataElementBytes& byte_elem = |
| elem.As<network::DataElementBytes>(); |
| EXPECT_EQ("client_id=clientId&account_hint=hint", |
| byte_elem.AsStringPiece()); |
| ASSERT_EQ(request.mode, network::mojom::RequestMode::kCors); |
| ASSERT_EQ(request.request_initiator, rpOrigin); |
| }); |
| test_url_loader_factory().SetInterceptor(interceptor); |
| |
| const char test_disconnect_json[] = R"({ |
| "account_id" : "accountId" |
| })"; |
| |
| GURL disconnect_endpoint(kTestDisconnectEndpoint); |
| AddResponse(disconnect_endpoint, net::HTTP_OK, "application/json", |
| test_disconnect_json); |
| |
| base::RunLoop run_loop; |
| FetchStatus disconnect_response; |
| std::optional<std::string> disconnect_account_id; |
| auto callback = base::BindLambdaForTesting( |
| [&](FetchStatus response, const std::string& account_id) { |
| disconnect_response = response; |
| disconnect_account_id = account_id; |
| run_loop.Quit(); |
| }); |
| |
| std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager(); |
| manager->SendDisconnectRequest(disconnect_endpoint, "hint", "clientId", |
| std::move(callback)); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(called); |
| EXPECT_EQ(ParseStatus::kSuccess, disconnect_response.parse_status); |
| EXPECT_EQ(net::HTTP_OK, disconnect_response.response_code); |
| ASSERT_TRUE(disconnect_account_id.has_value()); |
| EXPECT_EQ(*disconnect_account_id, "accountId"); |
| } |
| |
| } // namespace |
| |
| } // namespace content |