| // Copyright 2024 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/digital_credentials/digital_identity_request_impl.h" |
| |
| #include <optional> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_command_line.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "content/browser/webid/test/mock_digital_identity_provider.h" |
| #include "content/browser/webid/test/stub_digital_identity_provider.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/digital_identity_provider.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/test/test_render_frame_host.h" |
| #include "content/test/test_render_view_host.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/webid/digital_identity_request.mojom.h" |
| |
| namespace content { |
| namespace { |
| |
| constexpr char kOpenid4vpProtocol[] = "openid4vp"; |
| constexpr char kOpenid4vpSignedProtocol[] = "openid4vp-v1-signed"; |
| constexpr char kOpenid4vpUnsignedProtocol[] = "openid4vp-v1-unsigned"; |
| constexpr char kPreviewProtocol[] = "preview"; |
| |
| using base::Value; |
| using base::ValueView; |
| using testing::_; |
| using testing::DoAll; |
| using testing::Eq; |
| using testing::Optional; |
| using testing::WithArg; |
| |
| using InterstitialType = content::DigitalIdentityInterstitialType; |
| using DigitalCredentialCreateRequestPtr = |
| blink::mojom::DigitalCredentialCreateRequestPtr; |
| using DigitalCredentialCreateRequest = |
| blink::mojom::DigitalCredentialCreateRequest; |
| using DigitalCredentialGetRequestPtr = |
| blink::mojom::DigitalCredentialGetRequestPtr; |
| using DigitalCredentialGetRequest = blink::mojom::DigitalCredentialGetRequest; |
| using RequestDigitalIdentityStatus = blink::mojom::RequestDigitalIdentityStatus; |
| using DigitalIdentityCallback = |
| DigitalIdentityProvider::DigitalIdentityCallback; |
| using DigitalCredential = DigitalIdentityProvider::DigitalCredential; |
| using GetCallback = blink::mojom::DigitalIdentityRequest::GetCallback; |
| |
| // StubDigitalIdentityProvider which enables overriding |
| // DigitalIdentityProvider::IsLastCommittedOriginLowRisk(). |
| class TestDigitalIdentityProviderWithCustomRisk |
| : public StubDigitalIdentityProvider { |
| public: |
| explicit TestDigitalIdentityProviderWithCustomRisk(bool are_origins_low_risk) |
| : are_origins_low_risk_(are_origins_low_risk) {} |
| ~TestDigitalIdentityProviderWithCustomRisk() override = default; |
| |
| bool IsLastCommittedOriginLowRisk( |
| RenderFrameHost& render_frame_host) const override { |
| return are_origins_low_risk_; |
| } |
| |
| private: |
| bool are_origins_low_risk_; |
| }; |
| |
| base::Value ParseJsonAndCheck(const std::string& json) { |
| std::optional<base::Value> parsed = base::JSONReader::Read(json); |
| return parsed.has_value() ? std::move(*parsed) : base::Value(); |
| } |
| |
| base::Value GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition() { |
| constexpr char kJson[] = R"({ |
| "client_id": "digital-credentials.dev", |
| "client_id_scheme": "web-origin", |
| "response_type": "vp_token", |
| "nonce": "Q7pqzU98nsE9O9t--NpdmmeZSQOkmYKXsWCiJi39UGs=", |
| "presentation_definition": { |
| "id": "mDL-request-demo", |
| "input_descriptors": [ |
| { |
| "id": "org.iso.18013.5.1.mDL", |
| "format": { |
| "mso_mdoc": { |
| "alg": [ |
| "ES256" |
| ] |
| } |
| }, |
| "constraints": { |
| "limit_disclosure": "required", |
| "fields": [ |
| { |
| "path": [ |
| "$['org.iso.18013.5.1']['age_over_78']" |
| ], |
| "intent_to_retain": false |
| } |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateOnlyAgeOpenid4VpRequestWithDCQL() { |
| constexpr char kJson[] = R"({ |
| "response_type": "vp_token", |
| "response_mode": "w3c_dc_api", |
| "client_id": "web-origin:https://www.digital-credentials.dev", |
| "nonce": "d_xvsQ_PF1oPVZbjAfWu_xgwh3dJf_W5zgWB3U2xWw8", |
| "dcql_query": { |
| "credentials": [ |
| { |
| "id": "cred1", |
| "format": "mso_mdoc", |
| "meta": { |
| "doctype_value": "org.iso.18013.5.1.mDL" |
| }, |
| "claims": [ |
| { |
| "path": [ |
| "org.iso.18013.5.1", |
| "age_over_21" |
| ] |
| } |
| ] |
| } |
| ] |
| } |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateSignedOnlyAgeOpenid4VpRequestWithDCQL() { |
| constexpr char kJson[] = R"({ |
| "request": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiTUlJQ3FqQ0NBbEdnQXdJQkFnSVVXcVlEaTZjZy9YeEFDbDU1U1hJS3I5cVRGak13Q2dZSUtvWkl6ajBFQXdJd2VqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURVMXZkVzUwWVdsdUlGWnBaWGN4SERBYUJnTlZCQW9NRTBScFoybDBZV3dnUTNKbFpHVnVkR2xoYkhNeElEQWVCZ05WQkFNTUYyUnBaMmwwWVd3dFkzSmxaR1Z1ZEdsaGJITXVaR1YyTUI0WERUSTFNRFF5TlRFek5URXdPRm9YRFRNMU1EUXhNekV6TlRFd09Gb3dlakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY01EVTF2ZFc1MFlXbHVJRlpwWlhjeEhEQWFCZ05WQkFvTUUwUnBaMmwwWVd3Z1EzSmxaR1Z1ZEdsaGJITXhJREFlQmdOVkJBTU1GMlJwWjJsMFlXd3RZM0psWkdWdWRHbGhiSE11WkdWMk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVgxbEVSYzlqNHBlbjdvRmszVU1FSEJjUEhvVlFYWDdXdkRwUkhNM1czdDNjeFNLRlA3T3dRcVJHNlZWQ01QdzJqeXM0NHpQTHZDaCtWNndDQlZ4R2FhT0J0RENCc1RBZEJnTlZIUTRFRmdRVTAwRm1Kd2kvazNlT09CU2Q5MnM0K1dYbnpIOHdId1lEVlIwakJCZ3dGb0FVMDBGbUp3aS9rM2VPT0JTZDkyczQrV1huekg4d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3S2dZRFZSMFNCQ013SVlZZmFIUjBjSE02THk5a2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFpQmdOVkhSRUVHekFaZ2hka2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUVBdzA4bVozQUdtc1lKZnZ2Vldia0MwV1E2WVdVUlhXaFE2Vnh5ZlllQ2pnc0NIeElnLzh5TkJ6RDBNRndRcnBXWVhKTkxvbHdHYlpPSjM0TUI0eVdsemtvPSJdfQ.eyJyZXNwb25zZV90eXBlIjogInZwX3Rva2VuIiwgInJlc3BvbnNlX21vZGUiOiAiZGNfYXBpIiwgIm5vbmNlIjogIjNmRzI4SjByNEVlTzNibUcxYzNuRGFfa2NVLTEyTEo4RmZFUFZjNnVnME0iLCAiZGNxbF9xdWVyeSI6IHsiY3JlZGVudGlhbHMiOiBbeyJpZCI6ICJjcmVkMSIsICJmb3JtYXQiOiAibXNvX21kb2MiLCAibWV0YSI6IHsiZG9jdHlwZV92YWx1ZSI6ICJvcmcuaXNvLjE4MDEzLjUuMS5tREwifSwgImNsYWltcyI6IFt7InBhdGgiOiBbIm9yZy5pc28uMTgwMTMuNS4xIiwgImFnZV9vdmVyXzIxIl19XX1dfSwgImNsaWVudF9tZXRhZGF0YSI6IHsidnBfZm9ybWF0c19zdXBwb3J0ZWQiOiB7Im1zb19tZG9jIjogeyJpc3N1ZXJhdXRoX2FsZ192YWx1ZXMiOiBbLTddLCAiZGV2aWNlYXV0aF9hbGdfdmFsdWVzIjogWy03XX19fSwgImNsaWVudF9pZCI6ICJ4NTA5X3Nhbl9kbnM6ZGlnaXRhbC1jcmVkZW50aWFscy5kZXYiLCAiZXhwZWN0ZWRfb3JpZ2lucyI6IFsiaHR0cHM6Ly9kaWdpdGFsLWNyZWRlbnRpYWxzLmRldiJdfQ.iHaZvr69d57S3qc40MUOqnU04zpX-uRuAstKjCDYH6mCxzPTgy8YXs1WEWNY-xOatPlS74IvunAvJr7wJaYqvg" |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateSignedGivenNameOpenid4VpRequestWithDCQL() { |
| constexpr char kJson[] = R"({ |
| "request": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiTUlJQ3FqQ0NBbEdnQXdJQkFnSVVXcVlEaTZjZy9YeEFDbDU1U1hJS3I5cVRGak13Q2dZSUtvWkl6ajBFQXdJd2VqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURVMXZkVzUwWVdsdUlGWnBaWGN4SERBYUJnTlZCQW9NRTBScFoybDBZV3dnUTNKbFpHVnVkR2xoYkhNeElEQWVCZ05WQkFNTUYyUnBaMmwwWVd3dFkzSmxaR1Z1ZEdsaGJITXVaR1YyTUI0WERUSTFNRFF5TlRFek5URXdPRm9YRFRNMU1EUXhNekV6TlRFd09Gb3dlakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY01EVTF2ZFc1MFlXbHVJRlpwWlhjeEhEQWFCZ05WQkFvTUUwUnBaMmwwWVd3Z1EzSmxaR1Z1ZEdsaGJITXhJREFlQmdOVkJBTU1GMlJwWjJsMFlXd3RZM0psWkdWdWRHbGhiSE11WkdWMk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVgxbEVSYzlqNHBlbjdvRmszVU1FSEJjUEhvVlFYWDdXdkRwUkhNM1czdDNjeFNLRlA3T3dRcVJHNlZWQ01QdzJqeXM0NHpQTHZDaCtWNndDQlZ4R2FhT0J0RENCc1RBZEJnTlZIUTRFRmdRVTAwRm1Kd2kvazNlT09CU2Q5MnM0K1dYbnpIOHdId1lEVlIwakJCZ3dGb0FVMDBGbUp3aS9rM2VPT0JTZDkyczQrV1huekg4d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3S2dZRFZSMFNCQ013SVlZZmFIUjBjSE02THk5a2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFpQmdOVkhSRUVHekFaZ2hka2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUVBdzA4bVozQUdtc1lKZnZ2Vldia0MwV1E2WVdVUlhXaFE2Vnh5ZlllQ2pnc0NIeElnLzh5TkJ6RDBNRndRcnBXWVhKTkxvbHdHYlpPSjM0TUI0eVdsemtvPSJdfQ.eyJyZXNwb25zZV90eXBlIjogInZwX3Rva2VuIiwgInJlc3BvbnNlX21vZGUiOiAiZGNfYXBpIiwgIm5vbmNlIjogIk1iNzVFQTROdWdlZTRqdjdYVGZjWk1ZN1dLYUJ0eVB3YjJuTGpSb0NkTGsiLCAiZGNxbF9xdWVyeSI6IHsiY3JlZGVudGlhbHMiOiBbeyJpZCI6ICJjcmVkMSIsICJmb3JtYXQiOiAibXNvX21kb2MiLCAibWV0YSI6IHsiZG9jdHlwZV92YWx1ZSI6ICJvcmcuaXNvLjE4MDEzLjUuMS5tREwifSwgImNsYWltcyI6IFt7InBhdGgiOiBbIm9yZy5pc28uMTgwMTMuNS4xIiwgImdpdmVuX25hbWUiXX1dfV19LCAiY2xpZW50X21ldGFkYXRhIjogeyJ2cF9mb3JtYXRzX3N1cHBvcnRlZCI6IHsibXNvX21kb2MiOiB7Imlzc3VlcmF1dGhfYWxnX3ZhbHVlcyI6IFstN10sICJkZXZpY2VhdXRoX2FsZ192YWx1ZXMiOiBbLTddfX19LCAiY2xpZW50X2lkIjogIng1MDlfc2FuX2RuczpkaWdpdGFsLWNyZWRlbnRpYWxzLmRldiIsICJleHBlY3RlZF9vcmlnaW5zIjogWyJodHRwczovL2RpZ2l0YWwtY3JlZGVudGlhbHMuZGV2Il19.1JymSi56nHbnm2Ggye6YHsYCa3Mchmdf-WJUoEpjYGu3gMcHM50cfCMkUmxkvWV7E1R_ot2VAYLLBcSJRw_3fg" |
| |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateSignedGivenNameAndAgeOpenid4VpRequestWithDCQL() { |
| constexpr char kJson[] = R"({ |
| "request": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiTUlJQ3FqQ0NBbEdnQXdJQkFnSVVXcVlEaTZjZy9YeEFDbDU1U1hJS3I5cVRGak13Q2dZSUtvWkl6ajBFQXdJd2VqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURVMXZkVzUwWVdsdUlGWnBaWGN4SERBYUJnTlZCQW9NRTBScFoybDBZV3dnUTNKbFpHVnVkR2xoYkhNeElEQWVCZ05WQkFNTUYyUnBaMmwwWVd3dFkzSmxaR1Z1ZEdsaGJITXVaR1YyTUI0WERUSTFNRFF5TlRFek5URXdPRm9YRFRNMU1EUXhNekV6TlRFd09Gb3dlakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY01EVTF2ZFc1MFlXbHVJRlpwWlhjeEhEQWFCZ05WQkFvTUUwUnBaMmwwWVd3Z1EzSmxaR1Z1ZEdsaGJITXhJREFlQmdOVkJBTU1GMlJwWjJsMFlXd3RZM0psWkdWdWRHbGhiSE11WkdWMk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVgxbEVSYzlqNHBlbjdvRmszVU1FSEJjUEhvVlFYWDdXdkRwUkhNM1czdDNjeFNLRlA3T3dRcVJHNlZWQ01QdzJqeXM0NHpQTHZDaCtWNndDQlZ4R2FhT0J0RENCc1RBZEJnTlZIUTRFRmdRVTAwRm1Kd2kvazNlT09CU2Q5MnM0K1dYbnpIOHdId1lEVlIwakJCZ3dGb0FVMDBGbUp3aS9rM2VPT0JTZDkyczQrV1huekg4d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3S2dZRFZSMFNCQ013SVlZZmFIUjBjSE02THk5a2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFpQmdOVkhSRUVHekFaZ2hka2FXZHBkR0ZzTFdOeVpXUmxiblJwWVd4ekxtUmxkakFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUVBdzA4bVozQUdtc1lKZnZ2Vldia0MwV1E2WVdVUlhXaFE2Vnh5ZlllQ2pnc0NIeElnLzh5TkJ6RDBNRndRcnBXWVhKTkxvbHdHYlpPSjM0TUI0eVdsemtvPSJdfQ.eyJyZXNwb25zZV90eXBlIjogInZwX3Rva2VuIiwgInJlc3BvbnNlX21vZGUiOiAiZGNfYXBpIiwgIm5vbmNlIjogIlZHc1NWdWFDUUdYSEFaMktZVzB5QVMzVy02LTQycFI5bTlUa3lvYjd1UFkiLCAiZGNxbF9xdWVyeSI6IHsiY3JlZGVudGlhbHMiOiBbeyJpZCI6ICJjcmVkMSIsICJmb3JtYXQiOiAibXNvX21kb2MiLCAibWV0YSI6IHsiZG9jdHlwZV92YWx1ZSI6ICJvcmcuaXNvLjE4MDEzLjUuMS5tREwifSwgImNsYWltcyI6IFt7InBhdGgiOiBbIm9yZy5pc28uMTgwMTMuNS4xIiwgImdpdmVuX25hbWUiXX0sIHsicGF0aCI6IFsib3JnLmlzby4xODAxMy41LjEiLCAiYWdlX292ZXJfMjEiXX1dfV19LCAiY2xpZW50X21ldGFkYXRhIjogeyJ2cF9mb3JtYXRzX3N1cHBvcnRlZCI6IHsibXNvX21kb2MiOiB7Imlzc3VlcmF1dGhfYWxnX3ZhbHVlcyI6IFstN10sICJkZXZpY2VhdXRoX2FsZ192YWx1ZXMiOiBbLTddfX19LCAiY2xpZW50X2lkIjogIng1MDlfc2FuX2RuczpkaWdpdGFsLWNyZWRlbnRpYWxzLmRldiIsICJleHBlY3RlZF9vcmlnaW5zIjogWyJodHRwczovL2RpZ2l0YWwtY3JlZGVudGlhbHMuZGV2Il19.ZHtU19tEM1P5GNIl5oJTSyN6TQvMJAo7ruA1xHy-fvYanQLadoMVTTIuXgni6zMX2oSkZEBXp8ZD7wp-UJL1Pg" |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateVerifyPhoneNumberOpenid4VpRequest() { |
| constexpr char kJson[] = R"({ |
| "response_type": "vp_token", |
| "response_mode": "dc_api", |
| "client_id": "web-origin:https://www.digital-credentials.dev", |
| "nonce": "y9f67H0Kb2QF7nSbYh-XxBKkvGTCHk5MQo9OLBkKWD0", |
| "dcql_query": { |
| "credentials": [ |
| { |
| "claims": [ |
| { |
| "path": [ |
| "subscription_hint" |
| ], |
| "values": [ |
| 1 |
| ] |
| }, |
| { |
| "path": [ |
| "carrier_hint" |
| ], |
| "values": [ |
| "310250" |
| ] |
| }, |
| { |
| "path": [ |
| "android_carrier_hint" |
| ], |
| "values": [ |
| 7 |
| ] |
| } |
| ], |
| "format": "dc-authorization+sd-jwt", |
| "id": "aggregator1", |
| "meta": { |
| "credential_authorization_jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiTUlJQ3BUQ0NBa3VnQXdJQkFnSVVDOWZOSnBkVU1RWWRCbDFuaDgrUml0UndNRDh3Q2dZSUtvWkl6ajBFQXdJd2VERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURVMXZkVzUwWVdsdUlGWnBaWGN4R3pBWkJnTlZCQW9NRWtWNFlXMXdiR1VnUVdkbmNtVm5ZWFJ2Y2pFZk1CMEdBMVVFQXd3V1pYaGhiWEJzWlMxaFoyZHlaV2RoZEc5eUxtUmxkakFlRncweU5UQTFNVEV5TWpRd01EVmFGdzB6TlRBME1qa3lNalF3TURWYU1IZ3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSERBMU5iM1Z1ZEdGcGJpQldhV1YzTVJzd0dRWURWUVFLREJKRmVHRnRjR3hsSUVGblozSmxaMkYwYjNJeEh6QWRCZ05WQkFNTUZtVjRZVzF3YkdVdFlXZG5jbVZuWVhSdmNpNWtaWFl3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJRcW5LTGw5U2g4dFcwM0h5aVBnOVRUcGlyQVg2V2haKzlJSWhVWFJGcDlxRFM0eW5YeG1GbjMzWk5nMTlQR1VzRWpxNGwzam9Penh2cHhqWDRoL1JlbzRHeU1JR3ZNQjBHQTFVZERnUVdCQlFBV1I5czRrWFRjeHJPeTFLSE12UldTSkg5YmpBZkJnTlZIU01FR0RBV2dCUUFXUjlzNGtYVGN4ck95MUtITXZSV1NKSDliakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTRHQTFVZER3RUIvd1FFQXdJSGdEQXBCZ05WSFJJRUlqQWdoaDVvZEhSd2N6b3ZMMlY0WVcxd2JHVXRZV2RuY21WbllYUnZjaTVqYjIwd0lRWURWUjBSQkJvd0dJSVdaWGhoYlhCc1pTMWhaMmR5WldkaGRHOXlMbU52YlRBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlCeERROUZiby9EUVRkbVNaS0NURUlHOXZma0JkWU5jVHcxUkkzT0k2L25KUUloQUw1NmU3YkVNOTlSTTFTUDAyd3gzbHhxZFZCWnhiVEhJcllCQkY3Y0FzYjMiXX0.eyJpc3MiOiAiaHR0cHM6Ly9kY2FnZ3JlZ2F0b3IuZGV2IiwgIm5vbmNlIjogInk5ZjY3SDBLYjJRRjduU2JZaC1YeEJLa3ZHVENIazVNUW85T0xCa0tXRDAiLCAiZW5jcnlwdGVkX3Jlc3BvbnNlX2VuY192YWx1ZXNfc3VwcG9ydGVkIjogWyJBMTI4R0NNIl0sICJqd2tzIjogeyJrZXlzIjogW3sia3R5IjogIkVDIiwgInVzZSI6ICJlbmMiLCAiYWxnIjogIkVDREgtRVMiLCAia2lkIjogIjEiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiY2g1eFFhSUtCdjlPdG95Mmlmb2hMUWJTTWRlVE5paVFyWEVvcjRreHBtSSIsICJ5IjogInU2MlBuTkQwZUhEay1tRFFjOFI0YUEyRVdjRkE5VVo0YVpjOG1KZDlTX00ifV19LCAiY29uc2VudF9kYXRhIjogImV5SmpiMjV6Wlc1MFgzUmxlSFFpT2lBaVVtbGtaWElnY0hKdlkyVnpjMlZ6SUhsdmRYSWdjR1Z5YzI5dVlXd2daR0YwWVNCaFkyTnZjbVJwYm1jZ2RHOGdiM1Z5SUhCeWFYWmhZM2tnY0c5c2FXTjVJaXdnSW5CdmJHbGplVjlzYVc1cklqb2dJbWgwZEhCek9pOHZaR1YyWld4dmNHVnlMbUZ1WkhKdmFXUXVZMjl0TDJsa1pXNTBhWFI1TDJScFoybDBZV3d0WTNKbFpHVnVkR2xoYkhNdlkzSmxaR1Z1ZEdsaGJDMTJaWEpwWm1sbGNpSXNJQ0p3YjJ4cFkzbGZkR1Y0ZENJNklDSk1aV0Z5YmlCaFltOTFkQ0J3Y21sMllXTjVJSEJ2YkdsamVTSjkifQ.rlVyABcvR1a-g7eyPSKJBeIgrsUkIsVHNKAFrEKeeQ5Qyscys02T_z3I72g0jGqbAddEBgq9rLuncc7z3ayp-Q", |
| "vct_values": [ |
| "number-verification/verify/ts43" |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateGetPhoneNumberOpenid4VpRequest() { |
| constexpr char kJson[] = R"({ |
| "response_type": "vp_token", |
| "response_mode": "dc_api", |
| "client_id": "web-origin:https://www.digital-credentials.dev", |
| "nonce": "y9f67H0Kb2QF7nSbYh-XxBKkvGTCHk5MQo9OLBkKWD0", |
| "dcql_query": { |
| "credentials": [ |
| { |
| "format": "dc-authorization+sd-jwt", |
| "id": "aggregator1", |
| "meta": { |
| "credential_authorization_jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiTUlJQ3BUQ0NBa3VnQXdJQkFnSVVDOWZOSnBkVU1RWWRCbDFuaDgrUml0UndNRDh3Q2dZSUtvWkl6ajBFQXdJd2VERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTURVMXZkVzUwWVdsdUlGWnBaWGN4R3pBWkJnTlZCQW9NRWtWNFlXMXdiR1VnUVdkbmNtVm5ZWFJ2Y2pFZk1CMEdBMVVFQXd3V1pYaGhiWEJzWlMxaFoyZHlaV2RoZEc5eUxtUmxkakFlRncweU5UQTFNVEV5TWpRd01EVmFGdzB6TlRBME1qa3lNalF3TURWYU1IZ3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSERBMU5iM1Z1ZEdGcGJpQldhV1YzTVJzd0dRWURWUVFLREJKRmVHRnRjR3hsSUVGblozSmxaMkYwYjNJeEh6QWRCZ05WQkFNTUZtVjRZVzF3YkdVdFlXZG5jbVZuWVhSdmNpNWtaWFl3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJRcW5LTGw5U2g4dFcwM0h5aVBnOVRUcGlyQVg2V2haKzlJSWhVWFJGcDlxRFM0eW5YeG1GbjMzWk5nMTlQR1VzRWpxNGwzam9Penh2cHhqWDRoL1JlbzRHeU1JR3ZNQjBHQTFVZERnUVdCQlFBV1I5czRrWFRjeHJPeTFLSE12UldTSkg5YmpBZkJnTlZIU01FR0RBV2dCUUFXUjlzNGtYVGN4ck95MUtITXZSV1NKSDliakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTRHQTFVZER3RUIvd1FFQXdJSGdEQXBCZ05WSFJJRUlqQWdoaDVvZEhSd2N6b3ZMMlY0WVcxd2JHVXRZV2RuY21WbllYUnZjaTVqYjIwd0lRWURWUjBSQkJvd0dJSVdaWGhoYlhCc1pTMWhaMmR5WldkaGRHOXlMbU52YlRBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlCeERROUZiby9EUVRkbVNaS0NURUlHOXZma0JkWU5jVHcxUkkzT0k2L25KUUloQUw1NmU3YkVNOTlSTTFTUDAyd3gzbHhxZFZCWnhiVEhJcllCQkY3Y0FzYjMiXX0.eyJpc3MiOiAiaHR0cHM6Ly9kY2FnZ3JlZ2F0b3IuZGV2IiwgIm5vbmNlIjogImVnOXFPRjZjQXdsV1ZrTVNIRjREWkdJZF9xZVhLcG9yUzdPUnJUTE5RODAiLCAiZW5jcnlwdGVkX3Jlc3BvbnNlX2VuY192YWx1ZXNfc3VwcG9ydGVkIjogWyJBMTI4R0NNIl0sICJqd2tzIjogeyJrZXlzIjogW3sia3R5IjogIkVDIiwgInVzZSI6ICJlbmMiLCAiYWxnIjogIkVDREgtRVMiLCAia2lkIjogIjEiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiUThLT25XYzdWWDdkb3RuRU9jT0daOVFudUg5MlBFSWQwR0dDQ3lXT0R3TSIsICJ5IjogIm1LNjd2emRnOGxveHpQNWVkazVLb0FnNmZpenhoVXgyN3Q0cFdTb1lMVVUifV19LCAiY29uc2VudF9kYXRhIjogImV5SmpiMjV6Wlc1MFgzUmxlSFFpT2lBaVVtbGtaWElnY0hKdlkyVnpjMlZ6SUhsdmRYSWdjR1Z5YzI5dVlXd2daR0YwWVNCaFkyTnZjbVJwYm1jZ2RHOGdiM1Z5SUhCeWFYWmhZM2tnY0c5c2FXTjVJaXdnSW5CdmJHbGplVjlzYVc1cklqb2dJbWgwZEhCek9pOHZaR1YyWld4dmNHVnlMbUZ1WkhKdmFXUXVZMjl0TDJsa1pXNTBhWFI1TDJScFoybDBZV3d0WTNKbFpHVnVkR2xoYkhNdlkzSmxaR1Z1ZEdsaGJDMTJaWEpwWm1sbGNpSXNJQ0p3YjJ4cFkzbGZkR1Y0ZENJNklDSk1aV0Z5YmlCaFltOTFkQ0J3Y21sMllXTjVJSEJ2YkdsamVTSjkifQ.vtHXBcFG_lzTCfiVfrupmSd4k7CptvBAknq821A5QmNqGVQmnzmYUlTF6a9bFdigeE2q_yJRfchJoiHXSUM_Uw", |
| "vct_values": [ |
| "number-verification/device-phone-number/ts43" |
| ] |
| } |
| } |
| ] |
| } |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| base::Value GenerateOnlyAgePreviewRequest() { |
| constexpr char kJson[] = R"({ |
| "selector": { |
| "format": [ |
| "mdoc" |
| ], |
| "doctype": "org.iso.18013.5.1.mDL", |
| "fields": [ |
| { |
| "namespace": "org.iso.18013.5.1", |
| "name": "age_over_21", |
| "intentToRetain": false |
| } |
| ] |
| }, |
| "nonce": "vvm3Q1VN1tXybccprmZhbZFIjBGSB4VNMuqQfD4Uiko=", |
| "readerPublicKey": "BMK9ink7wCHIKXxxWQy-S6TLN4jo1ab7NBlC-lSvqqMUmgMSadLa9PYYDocWitOmafZqWmZc5lQvdCZQx5mTNvs=" |
| })"; |
| |
| return ParseJsonAndCheck(kJson); |
| } |
| |
| // Does depth-first traversal of nested dicts rooted at `root`. Returns first |
| // matching base::Value with key `find_key`. |
| base::Value* FindValueWithKey(base::Value& root, const std::string& find_key) { |
| if (root.is_list()) { |
| base::Value::List& list = root.GetList(); |
| for (base::Value& list_item : list) { |
| if (base::Value* out = FindValueWithKey(list_item, find_key)) { |
| return out; |
| } |
| } |
| return nullptr; |
| } |
| |
| if (root.is_dict()) { |
| base::Value::Dict& dict = root.GetDict(); |
| for (auto it : dict) { |
| if (it.first == find_key) { |
| return &it.second; |
| } |
| if (base::Value* out = FindValueWithKey(it.second, find_key)) { |
| return out; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool HasNoListElements(const base::Value* value) { |
| return !value || !value->is_list() || value->GetList().size() == 0u; |
| } |
| |
| bool IsNonEmptyList(const base::Value* value) { |
| return !HasNoListElements(value); |
| } |
| |
| // Removes `find_key` if present from `dict`. Ignores nested base::Value::Dicts. |
| void RemoveDictKey(base::Value::Dict& dict, const std::string& find_key) { |
| for (auto it = dict.begin(); it != dict.end(); ++it) { |
| if (it->first == find_key) { |
| dict.erase(it); |
| break; |
| } |
| } |
| } |
| |
| // Used to modify an Openid4VpRequest with Presentation Definition on the fly. |
| bool SetPathItem(base::Value& to_modify, const std::string& path_item) { |
| base::Value* paths = FindValueWithKey(to_modify, "path"); |
| if (HasNoListElements(paths)) { |
| return false; |
| } |
| paths->GetList().resize(0); |
| paths->GetList().Append(path_item); |
| return true; |
| } |
| |
| // Used to modify an Openid4VpRequest with DCQL on the fly. |
| bool SetDCQLPathItem(base::Value& to_modify, |
| const std::string& field_name_value) { |
| base::Value* paths = FindValueWithKey(to_modify, "path"); |
| if (HasNoListElements(paths)) { |
| return false; |
| } |
| paths->GetList().resize(1); |
| paths->GetList().Append(field_name_value); |
| return true; |
| } |
| |
| // Used to modify a Preview on the fly. |
| bool SetFieldNameValue(base::Value& to_modify, |
| const std::string& field_name_value) { |
| base::Value* fields = FindValueWithKey(to_modify, "fields"); |
| if (HasNoListElements(fields)) { |
| return false; |
| } |
| fields->GetList().front().GetDict().Set("name", field_name_value); |
| return true; |
| } |
| |
| } // anonymous namespace |
| |
| class DigitalIdentityRequestImplInterstitialTest |
| : public RenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| RenderViewHostTestHarness::SetUp(); |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kWebIdentityDigitalCredentials, {{"dialog", ""}}); |
| } |
| |
| std::optional<InterstitialType> ComputeInterstitialType( |
| const std::string& protocol, |
| base::Value request_data, |
| bool are_origins_low_risk = false) { |
| auto provider = std::make_unique<TestDigitalIdentityProviderWithCustomRisk>( |
| are_origins_low_risk); |
| DigitalCredentialGetRequestPtr digital_credential_request = |
| DigitalCredentialGetRequest::New(); |
| digital_credential_request->protocol = protocol; |
| digital_credential_request->data = std::move(request_data); |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.emplace_back(std::move(digital_credential_request)); |
| return DigitalIdentityRequestImpl::ComputeInterstitialType( |
| *main_rfh(), provider.get(), std::move(requests)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_OnlyAgeOver) { |
| EXPECT_EQ(ComputeInterstitialType( |
| kOpenid4vpProtocol, |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition()), |
| std::nullopt); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_OnlyAgeInYears) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| ASSERT_TRUE(SetPathItem(request, "$['org.iso.18013.5.1']['age_in_years']")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_OnlyAgeBirthYear) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| ASSERT_TRUE(SetPathItem(request, "$['org.iso.18013.5.1']['age_birth_year']")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_OnlyBirthDate) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| ASSERT_TRUE(SetPathItem(request, "$['org.iso.18013.5.1']['birth_date']")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| base::Value GenerateNonAgeOpenid4VpRequest() { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| CHECK(SetPathItem(request, "$['org.iso.18013.5.1']['given_name']")); |
| return request; |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_OnlyNonAgeDataElement) { |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, |
| GenerateNonAgeOpenid4VpRequest()), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_LowRiskOriginTakesPrecedenceOverRequestType) { |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, |
| GenerateNonAgeOpenid4VpRequest(), |
| /*are_origins_low_risk=*/true), |
| std::nullopt); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_EmptyPathList) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* paths = FindValueWithKey(request, "path"); |
| ASSERT_TRUE(IsNonEmptyList(paths)); |
| paths->GetList().resize(0); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_RequestMultiplePaths) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* paths = FindValueWithKey(request, "path"); |
| ASSERT_TRUE(IsNonEmptyList(paths)); |
| |
| base::Value::List& path_list = paths->GetList(); |
| path_list.Append(path_list.front().Clone()); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_NoPath) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* fields = FindValueWithKey(request, "fields"); |
| ASSERT_TRUE(IsNonEmptyList(fields)); |
| RemoveDictKey(fields->GetList().front().GetDict(), "path"); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_EmptyFieldsList) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* fields = FindValueWithKey(request, "fields"); |
| ASSERT_TRUE(IsNonEmptyList(fields)); |
| fields->GetList().resize(0); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_RequestMultipleAgeAssertions) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* fields = FindValueWithKey(request, "fields"); |
| ASSERT_TRUE(IsNonEmptyList(fields)); |
| |
| base::Value new_field = ParseJsonAndCheck(R"({ |
| "path": [ |
| "$['org.iso.18013.5.1']['age_over_60']" |
| ] |
| })"); |
| fields->GetList().Append(std::move(new_field)); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_RequestMultipleFields) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* fields = FindValueWithKey(request, "fields"); |
| ASSERT_TRUE(IsNonEmptyList(fields)); |
| |
| base::Value new_field = ParseJsonAndCheck(R"({ |
| "path": [ |
| "$['org.iso.18013.5.1']['given_name']" |
| ] |
| })"); |
| fields->GetList().Append(std::move(new_field)); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_NoConstraints) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* input_descriptors = |
| FindValueWithKey(request, "input_descriptors"); |
| ASSERT_TRUE(IsNonEmptyList(input_descriptors)); |
| RemoveDictKey(input_descriptors->GetList().front().GetDict(), "constraints"); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_EmptyInputDescriptorList) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* input_descriptors = |
| FindValueWithKey(request, "input_descriptors"); |
| ASSERT_TRUE(IsNonEmptyList(input_descriptors)); |
| input_descriptors->GetList().resize(0); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_RequestMultipleDocuments) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* input_descriptors = |
| FindValueWithKey(request, "input_descriptors"); |
| ASSERT_TRUE(IsNonEmptyList(input_descriptors)); |
| |
| base::Value::List& input_descriptor_list = input_descriptors->GetList(); |
| input_descriptor_list.Append(input_descriptor_list.front().Clone()); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_NonMdlInputDescriptorId) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| base::Value* input_descriptors = |
| FindValueWithKey(request, "input_descriptors"); |
| ASSERT_TRUE(IsNonEmptyList(input_descriptors)); |
| |
| base::Value::List& input_descriptor_list = input_descriptors->GetList(); |
| ASSERT_TRUE(input_descriptor_list.front().is_dict()); |
| input_descriptor_list.front().GetDict().Set("id", "not_mdl"); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolPresentationDefinition_ComputeInterstitialType_NoPresentationDefinition) { |
| base::Value request = |
| GenerateOnlyAgeOpenid4VpRequestWithPresentationDefinition(); |
| RemoveDictKey(request.GetDict(), "presentation_definition"); |
| |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| PreviewProtocol_ComputeInterstitialType_OnlyAgeOver) { |
| EXPECT_EQ(ComputeInterstitialType(kPreviewProtocol, |
| GenerateOnlyAgePreviewRequest()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| PreviewProtocol_ComputeInterstitialType_OnlyAgeInYears) { |
| base::Value request = GenerateOnlyAgePreviewRequest(); |
| ASSERT_TRUE(SetFieldNameValue(request, "age_in_years")); |
| EXPECT_EQ(ComputeInterstitialType(kPreviewProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| PreviewProtocol_ComputeInterstitialType_OnlyAgeBirthYear) { |
| base::Value request = GenerateOnlyAgePreviewRequest(); |
| ASSERT_TRUE(SetFieldNameValue(request, "age_birth_year")); |
| EXPECT_EQ(ComputeInterstitialType(kPreviewProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| PreviewProtocol_ComputeInterstitialType_OnlyBirthDate) { |
| base::Value request = GenerateOnlyAgePreviewRequest(); |
| ASSERT_TRUE(SetFieldNameValue(request, "birth_date")); |
| EXPECT_EQ(ComputeInterstitialType(kPreviewProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| PreviewProtocol_ComputeInterstitialType_GivenName) { |
| base::Value request = GenerateOnlyAgePreviewRequest(); |
| ASSERT_TRUE(SetFieldNameValue(request, "given_name")); |
| EXPECT_EQ(ComputeInterstitialType(kPreviewProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_OnlyAgeOver) { |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, |
| GenerateOnlyAgeOpenid4VpRequestWithDCQL()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_OnlyAgeBirthYear) { |
| base::Value request = GenerateOnlyAgeOpenid4VpRequestWithDCQL(); |
| ASSERT_TRUE(SetDCQLPathItem(request, "age_birth_year")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_OnlyBirthDate) { |
| base::Value request = GenerateOnlyAgeOpenid4VpRequestWithDCQL(); |
| ASSERT_TRUE(SetDCQLPathItem(request, "birth_date")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_GivenName) { |
| base::Value request = GenerateOnlyAgeOpenid4VpRequestWithDCQL(); |
| ASSERT_TRUE(SetDCQLPathItem(request, "given_name")); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpUnsignedProtocolDCQL_ComputeInterstitialType_OnlyAgeOver) { |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpUnsignedProtocol, |
| GenerateOnlyAgeOpenid4VpRequestWithDCQL()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_GivenNameAndAgeOver) { |
| base::Value request = ParseJsonAndCheck(R"({ |
| "response_type": "vp_token", |
| "response_mode": "dc_api", |
| "nonce": "EReTrXMsLOF7BTUnvmiuYqIbqc9zgEcHON9qalEKtP4", |
| "dcql_query": { |
| "credentials": [ |
| { |
| "id": "cred1", |
| "format": "mso_mdoc", |
| "meta": { |
| "doctype_value": "org.iso.18013.5.1.mDL" |
| }, |
| "claims": [ |
| { |
| "path": [ |
| "org.iso.18013.5.1", |
| "given_name" |
| ] |
| }, |
| { |
| "path": [ |
| "org.iso.18013.5.1", |
| "age_over_21" |
| ] |
| } |
| ] |
| } |
| ] |
| } |
| })"); |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, std::move(request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpAndPreviewProtocol_ComputeInterstitialType_AgeOver) { |
| base::Value openid4vp_request = GenerateOnlyAgeOpenid4VpRequestWithDCQL(); |
| base::Value preview_request = GenerateOnlyAgePreviewRequest(); |
| |
| DigitalCredentialGetRequestPtr request1 = DigitalCredentialGetRequest::New(); |
| request1->protocol = kOpenid4vpProtocol; |
| request1->data = std::move(openid4vp_request); |
| |
| DigitalCredentialGetRequestPtr request2 = DigitalCredentialGetRequest::New(); |
| request2->protocol = kPreviewProtocol; |
| request2->data = std::move(preview_request); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.emplace_back(std::move(request1)); |
| requests.emplace_back(std::move(request2)); |
| |
| auto provider = std::make_unique<TestDigitalIdentityProviderWithCustomRisk>( |
| /*are_origins_low_risk=*/false); |
| EXPECT_EQ(DigitalIdentityRequestImpl::ComputeInterstitialType( |
| *main_rfh(), provider.get(), std::move(requests)), |
| std::nullopt); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpAndPreviewProtocol_ComputeInterstitialType_AgeOverAndGivenName) { |
| base::Value openid4vp_request = GenerateOnlyAgeOpenid4VpRequestWithDCQL(); |
| base::Value preview_request = GenerateOnlyAgePreviewRequest(); |
| ASSERT_TRUE(SetFieldNameValue(preview_request, "given_name")); |
| |
| DigitalCredentialGetRequestPtr request1 = DigitalCredentialGetRequest::New(); |
| request1->protocol = kOpenid4vpProtocol; |
| request1->data = std::move(openid4vp_request); |
| |
| DigitalCredentialGetRequestPtr request2 = DigitalCredentialGetRequest::New(); |
| request2->protocol = kPreviewProtocol; |
| request2->data = std::move(preview_request); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.emplace_back(std::move(request1)); |
| requests.emplace_back(std::move(request2)); |
| |
| auto provider = std::make_unique<TestDigitalIdentityProviderWithCustomRisk>( |
| /*are_origins_low_risk=*/false); |
| EXPECT_EQ(DigitalIdentityRequestImpl::ComputeInterstitialType( |
| *main_rfh(), provider.get(), std::move(requests)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_MalformedRequest) { |
| // Malformed request that's missing the claim_name entry. |
| base::Value malformed_request = ParseJsonAndCheck(R"({ |
| "response_type": "vp_token", |
| "response_mode": "w3c_dc_api", |
| "client_id": "web-origin:https://www.digital-credentials.dev", |
| "nonce": "CL0BDiED_T5qDttEddJASo8Ft5yR9C0wmLy6WFtHsCQ", |
| "dcql_query": { |
| "credentials": [ |
| { |
| "id": "cred1", |
| "format": "mso_mdoc", |
| "meta": { |
| "doctype_value": "org.iso.18013.5.1.mDL" |
| }, |
| "claims": [ |
| { |
| "namespace": "org.iso.18013.5.1", |
| }, |
| ] |
| } |
| ] |
| } |
| })"); |
| EXPECT_EQ( |
| ComputeInterstitialType(kOpenid4vpProtocol, std::move(malformed_request)), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_VerifyPhoneNumber) { |
| EXPECT_EQ( |
| ComputeInterstitialType(kOpenid4vpProtocol, |
| GenerateVerifyPhoneNumberOpenid4VpRequest()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolDCQL_ComputeInterstitialType_GetPhoneNumber) { |
| EXPECT_EQ(ComputeInterstitialType(kOpenid4vpProtocol, |
| GenerateGetPhoneNumberOpenid4VpRequest()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolSignedDCQL_ComputeInterstitialType_OnlyAgeOver) { |
| EXPECT_EQ( |
| ComputeInterstitialType(kOpenid4vpSignedProtocol, |
| GenerateSignedOnlyAgeOpenid4VpRequestWithDCQL()), |
| std::nullopt); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolSignedDCQL_ComputeInterstitialType_GivenName) { |
| EXPECT_EQ(ComputeInterstitialType( |
| kOpenid4vpSignedProtocol, |
| GenerateSignedGivenNameOpenid4VpRequestWithDCQL()), |
| InterstitialType::kLowRisk); |
| } |
| |
| TEST_F( |
| DigitalIdentityRequestImplInterstitialTest, |
| Openid4VpProtocolSignedDCQL_ComputeInterstitialType_GivenNameAndAgeOver) { |
| EXPECT_EQ(ComputeInterstitialType( |
| kOpenid4vpSignedProtocol, |
| GenerateSignedGivenNameAndAgeOpenid4VpRequestWithDCQL()), |
| InterstitialType::kLowRisk); |
| } |
| |
| class DigitalIdentityRequestImplWithCreationEnabledTest |
| : public RenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| RenderViewHostTestHarness::SetUp(); |
| digital_identity_request_impl_ = DigitalIdentityRequestImpl::CreateInstance( |
| *web_contents()->GetPrimaryMainFrame(), |
| request_remote_.BindNewPipeAndPassReceiver()); |
| |
| content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame()) |
| ->SimulateUserActivation(); |
| |
| command_line_.GetProcessCommandLine()->AppendSwitch( |
| switches::kUseFakeUIForDigitalIdentity); |
| } |
| |
| void TearDown() override { |
| // Reset here to avoid dangling pointer upon the destruction of the rvh. |
| digital_identity_request_impl_ = nullptr; |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| DigitalIdentityRequestImpl* digital_identity_request_impl() { |
| return digital_identity_request_impl_.get(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{ |
| features::kWebIdentityDigitalCredentialsCreation}; |
| base::test::ScopedCommandLine command_line_; |
| |
| mojo::Remote<blink::mojom::DigitalIdentityRequest> request_remote_; |
| base::WeakPtr<DigitalIdentityRequestImpl> digital_identity_request_impl_; |
| }; |
| |
| TEST_F(DigitalIdentityRequestImplWithCreationEnabledTest, |
| ShouldReturnErrorWhenNoRequest) { |
| base::MockCallback<DigitalIdentityRequestImpl::CreateCallback> callback; |
| |
| EXPECT_CALL(callback, |
| Run(RequestDigitalIdentityStatus::kErrorNoRequests, _, _)); |
| digital_identity_request_impl()->Create({}, callback.Get()); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplWithCreationEnabledTest, |
| ShouldReturnErrorWhenAnotherRequestIsInFlight) { |
| DigitalCredentialCreateRequestPtr digital_credential_request1 = |
| DigitalCredentialCreateRequest::New(); |
| digital_credential_request1->protocol = "protocol1"; |
| base::Value::Dict request1_data; |
| request1_data.Set("data", "request data 1"); |
| digital_credential_request1->data = base::Value(std::move(request1_data)); |
| |
| DigitalCredentialCreateRequestPtr digital_credential_request2 = |
| DigitalCredentialCreateRequest::New(); |
| digital_credential_request2->protocol = "protocol2"; |
| base::Value::Dict request2_data; |
| request2_data.Set("data", "request data 2"); |
| digital_credential_request2->data = base::Value(std::move(request2_data)); |
| |
| std::vector<blink::mojom::DigitalCredentialCreateRequestPtr> requests1; |
| requests1.push_back(std::move(digital_credential_request1)); |
| digital_identity_request_impl()->Create(std::move(requests1), |
| base::DoNothing()); |
| |
| base::MockCallback<DigitalIdentityRequestImpl::CreateCallback> callback; |
| EXPECT_CALL(callback, |
| Run(RequestDigitalIdentityStatus::kErrorTooManyRequests, _, _)); |
| std::vector<blink::mojom::DigitalCredentialCreateRequestPtr> requests2; |
| requests2.push_back(std::move(digital_credential_request2)); |
| digital_identity_request_impl()->Create(std::move(requests2), callback.Get()); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplWithCreationEnabledTest, |
| ShouldSucceedWhenValidRequest) { |
| const std::string kProtocol = "protocol"; |
| base::MockCallback<DigitalIdentityRequestImpl::CreateCallback> callback; |
| DigitalCredentialCreateRequestPtr digital_credential_request = |
| DigitalCredentialCreateRequest::New(); |
| digital_credential_request->protocol = kProtocol; |
| base::Value::Dict request_data; |
| request_data.Set("data", "request data"); |
| digital_credential_request->data = base::Value(std::move(request_data)); |
| |
| std::vector<blink::mojom::DigitalCredentialCreateRequestPtr> requests; |
| requests.push_back(std::move(digital_credential_request)); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(RequestDigitalIdentityStatus::kSuccess, |
| testing::Optional(kProtocol), _)) |
| .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); |
| digital_identity_request_impl()->Create(std::move(requests), callback.Get()); |
| run_loop.Run(); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplWithCreationEnabledTest, |
| ShouldReturnErrorWhenAbort) { |
| const std::string kProtocol = "protocol"; |
| base::MockCallback<DigitalIdentityRequestImpl::CreateCallback> callback; |
| DigitalCredentialCreateRequestPtr digital_credential_request = |
| DigitalCredentialCreateRequest::New(); |
| digital_credential_request->protocol = kProtocol; |
| base::Value::Dict request_data; |
| request_data.Set("data", "request data"); |
| digital_credential_request->data = base::Value(std::move(request_data)); |
| |
| std::vector<blink::mojom::DigitalCredentialCreateRequestPtr> requests; |
| requests.push_back(std::move(digital_credential_request)); |
| |
| EXPECT_CALL(callback, |
| Run(RequestDigitalIdentityStatus::kErrorCanceled, _, _)); |
| digital_identity_request_impl()->Create(std::move(requests), callback.Get()); |
| digital_identity_request_impl()->Abort(); |
| } |
| |
| class ContentBrowserClientWithMockDigitalIdentityProvider |
| : public ContentBrowserClient { |
| public: |
| ContentBrowserClientWithMockDigitalIdentityProvider() = default; |
| ~ContentBrowserClientWithMockDigitalIdentityProvider() override = default; |
| |
| // ContentBrowserClient overrides: |
| std::unique_ptr<DigitalIdentityProvider> CreateDigitalIdentityProvider() |
| override { |
| return std::move(provider_); |
| } |
| |
| void SetDigitalIdentityProvider( |
| std::unique_ptr<DigitalIdentityProvider> provider) { |
| provider_ = std::move(provider); |
| } |
| |
| private: |
| std::unique_ptr<DigitalIdentityProvider> provider_; |
| }; |
| |
| class DigitalIdentityRequestImplTest : public RenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| RenderViewHostTestHarness::SetUp(); |
| |
| auto mock_digital_identity_provider = |
| std::make_unique<MockDigitalIdentityProvider>(); |
| mock_digital_identity_provider_ = mock_digital_identity_provider.get(); |
| content_browser_client_.SetDigitalIdentityProvider( |
| std::move(mock_digital_identity_provider)); |
| content::SetBrowserClientForTesting(&content_browser_client_); |
| |
| digital_identity_request_impl_ = DigitalIdentityRequestImpl::CreateInstance( |
| *web_contents()->GetPrimaryMainFrame(), |
| request_remote_.BindNewPipeAndPassReceiver()); |
| |
| content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame()) |
| ->SimulateUserActivation(); |
| |
| // Tests in this fixture don't test the dialog behaviour. |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kWebIdentityDigitalCredentials, {{"dialog", "no_dialog"}}); |
| } |
| |
| void TearDown() override { |
| // Reset here to avoid dangling pointer upon the destruction of the rvh. |
| digital_identity_request_impl_ = nullptr; |
| mock_digital_identity_provider_ = nullptr; |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| DigitalIdentityRequestImpl* digital_identity_request_impl() { |
| return digital_identity_request_impl_.get(); |
| } |
| |
| MockDigitalIdentityProvider* mock_digital_identity_provider() { |
| return mock_digital_identity_provider_; |
| } |
| |
| void reset_provider_pointer() { mock_digital_identity_provider_ = nullptr; } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| // base::test::ScopedCommandLine command_line_; |
| |
| raw_ptr<MockDigitalIdentityProvider> mock_digital_identity_provider_; |
| ContentBrowserClientWithMockDigitalIdentityProvider content_browser_client_; |
| |
| mojo::Remote<blink::mojom::DigitalIdentityRequest> request_remote_; |
| base::WeakPtr<DigitalIdentityRequestImpl> digital_identity_request_impl_; |
| }; |
| |
| TEST_F(DigitalIdentityRequestImplTest, ShouldGetWithProperFormatting) { |
| const std::string kProtocol = "protocol"; |
| |
| DigitalCredentialGetRequestPtr digital_credential_request = |
| DigitalCredentialGetRequest::New(); |
| digital_credential_request->protocol = kProtocol; |
| base::Value::Dict request_data; |
| request_data.Set("data", "request data"); |
| digital_credential_request->data = base::Value(std::move(request_data)); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.push_back(std::move(digital_credential_request)); |
| |
| base::RunLoop run_loop; |
| // Intercept the `Get()` call and verify that the request is formatted |
| // properly. |
| EXPECT_CALL(*mock_digital_identity_provider(), Get) |
| .WillOnce(DoAll(WithArg<2>([](ValueView request) { |
| Value::Dict dict = request.ToValue().GetDict().Clone(); |
| EXPECT_TRUE(dict.contains("requests")); |
| for (const Value& req : *dict.FindList("requests")) { |
| EXPECT_TRUE(req.GetDict().contains("protocol")); |
| EXPECT_TRUE(req.GetDict().contains("data")); |
| EXPECT_TRUE(req.GetDict().Find("data")->is_dict()); |
| } |
| }), |
| base::test::RunOnceClosure(run_loop.QuitClosure()))); |
| digital_identity_request_impl()->Get(std::move(requests), base::DoNothing()); |
| run_loop.Run(); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplTest, ShouldGetAndReturnProtocolInRequest) { |
| const std::string kProtocol = "protocol"; |
| const Value kResponseData(Value::Dict().Set("token", "token data")); |
| |
| DigitalCredentialGetRequestPtr digital_credential_request = |
| DigitalCredentialGetRequest::New(); |
| digital_credential_request->protocol = kProtocol; |
| base::Value::Dict request_data; |
| request_data.Set("data", "request data"); |
| digital_credential_request->data = base::Value(std::move(request_data)); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.push_back(std::move(digital_credential_request)); |
| |
| base::RunLoop run_loop; |
| |
| // Simulate a provider that returns a response without a protocol. |
| EXPECT_CALL(*mock_digital_identity_provider(), Get) |
| .WillOnce( |
| WithArg<3>([this, &kResponseData](DigitalIdentityCallback callback) { |
| // Running the `callback` will destroy the provider, reset the |
| // pointer to avoid dangling pointers after invoking the callback. |
| reset_provider_pointer(); |
| |
| std::move(callback).Run( |
| DigitalCredential(std::nullopt, kResponseData.Clone())); |
| })); |
| |
| base::MockCallback<GetCallback> mock_callback; |
| // The protocol in the request should be used when invoking the callback, |
| // since no protocol was available in the response. |
| EXPECT_CALL(mock_callback, Run(RequestDigitalIdentityStatus::kSuccess, |
| Optional(kProtocol), _)) |
| .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); |
| |
| digital_identity_request_impl()->Get(std::move(requests), |
| mock_callback.Get()); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplTest, ShouldGetAndReturnProtocolInResponse) { |
| const std::string kProtocolInRequest = "protocol_in_request"; |
| const std::string kProtocolInResponse = "protocol_in_response"; |
| const Value kResponseData(Value::Dict().Set("token", "token data")); |
| |
| DigitalCredentialGetRequestPtr digital_credential_request = |
| DigitalCredentialGetRequest::New(); |
| digital_credential_request->protocol = kProtocolInRequest; |
| base::Value::Dict request_data; |
| request_data.Set("data", "request data"); |
| digital_credential_request->data = base::Value(std::move(request_data)); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| requests.push_back(std::move(digital_credential_request)); |
| |
| base::RunLoop run_loop; |
| |
| // Simulate a provider that returns a response with a protocol. |
| EXPECT_CALL(*mock_digital_identity_provider(), Get) |
| .WillOnce(WithArg<3>([this, &kProtocolInResponse, |
| &kResponseData](DigitalIdentityCallback callback) { |
| // Running the `callback` will destroy the provider, reset the pointer |
| // to avoid dangling pointers after invoking the callback. |
| reset_provider_pointer(); |
| |
| std::move(callback).Run( |
| DigitalCredential(kProtocolInResponse, kResponseData.Clone())); |
| })); |
| |
| base::MockCallback<GetCallback> mock_callback; |
| // The protocol in the response should be used when invoking the callback. |
| EXPECT_CALL(mock_callback, Run(RequestDigitalIdentityStatus::kSuccess, |
| Optional(kProtocolInResponse), _)) |
| .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); |
| |
| digital_identity_request_impl()->Get(std::move(requests), |
| mock_callback.Get()); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplTest, |
| ShouldGetWhenMultipleRequestsAndReturnProtocolInResponse) { |
| const Value kResponseData(Value::Dict().Set("token", "token data")); |
| const std::string kProtocolInResponse = "protocol1"; |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| |
| DigitalCredentialGetRequestPtr request1 = DigitalCredentialGetRequest::New(); |
| request1->protocol = "protocol1"; |
| base::Value::Dict request1_data; |
| request1_data.Set("data", "request1 data"); |
| request1->data = base::Value(std::move(request1_data)); |
| |
| DigitalCredentialGetRequestPtr request2 = DigitalCredentialGetRequest::New(); |
| request2->protocol = "protocol2"; |
| base::Value::Dict request2_data; |
| request2_data.Set("data", "request2 data"); |
| request2->data = base::Value(std::move(request2_data)); |
| |
| requests.push_back(std::move(request1)); |
| requests.push_back(std::move(request2)); |
| |
| base::RunLoop run_loop; |
| |
| // Simulate a provider that returns a response without a protocol. |
| EXPECT_CALL(*mock_digital_identity_provider(), Get) |
| .WillOnce(WithArg<3>([this, &kProtocolInResponse, |
| &kResponseData](DigitalIdentityCallback callback) { |
| // Running the `callback` will destroy the provider, reset the |
| // pointer to avoid dangling pointers after invoking the callback. |
| reset_provider_pointer(); |
| |
| std::move(callback).Run( |
| DigitalCredential(kProtocolInResponse, kResponseData.Clone())); |
| })); |
| |
| base::MockCallback<GetCallback> mock_callback; |
| // The protocol in the response should be used when invoking the callback. |
| EXPECT_CALL(mock_callback, Run(RequestDigitalIdentityStatus::kSuccess, |
| Optional(kProtocolInResponse), _)) |
| .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); |
| |
| digital_identity_request_impl()->Get(std::move(requests), |
| mock_callback.Get()); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(DigitalIdentityRequestImplTest, |
| ShouldErrorWhenMultipleRequestsAndNoProtocolInResponse) { |
| const Value kResponseData(Value::Dict().Set("token", "token data")); |
| |
| std::vector<DigitalCredentialGetRequestPtr> requests; |
| |
| DigitalCredentialGetRequestPtr request1 = DigitalCredentialGetRequest::New(); |
| request1->protocol = "protocol1"; |
| base::Value::Dict request1_data; |
| request1_data.Set("data", "request1 data"); |
| request1->data = base::Value(std::move(request1_data)); |
| |
| DigitalCredentialGetRequestPtr request2 = DigitalCredentialGetRequest::New(); |
| request2->protocol = "protocol2"; |
| base::Value::Dict request2_data; |
| request2_data.Set("data", "request2 data"); |
| request2->data = base::Value(std::move(request2_data)); |
| |
| requests.push_back(std::move(request1)); |
| requests.push_back(std::move(request2)); |
| |
| base::RunLoop run_loop; |
| |
| // Simulate a provider that returns a response without a protocol. |
| EXPECT_CALL(*mock_digital_identity_provider(), Get) |
| .WillOnce( |
| WithArg<3>([this, &kResponseData](DigitalIdentityCallback callback) { |
| // Running the `callback` will destroy the provider, reset the |
| // pointer to avoid dangling pointers after invoking the callback. |
| reset_provider_pointer(); |
| |
| std::move(callback).Run(DigitalCredential( |
| /*protocol=*/std::nullopt, kResponseData.Clone())); |
| })); |
| |
| base::MockCallback<GetCallback> mock_callback; |
| // The callback should be invoked with an error since the digital wallet |
| // response indicates that it doesn't support multiple requests. |
| EXPECT_CALL(mock_callback, Run(RequestDigitalIdentityStatus::kError, _, _)) |
| .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); |
| |
| digital_identity_request_impl()->Get(std::move(requests), |
| mock_callback.Get()); |
| |
| run_loop.Run(); |
| } |
| |
| } // namespace content |