Commit b9fc0761 authored by jdoerrie's avatar jdoerrie Committed by Commit Bot

Add Support for App Display Names to GetShownOriginAndLinkUrl

This change adds support for app display names to Password UI Utils.
This is useful for the more human friendly display of Android
credentials, as this will display the Play Store name of the App, if
available.

Bug: 628988, 617094, 679434
Change-Id: Ide7bc694805ce40cbbccf8e8496c32bcb677de25
Reviewed-on: https://chromium-review.googlesource.com/567936
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarHector Carmona <hcarmona@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486721}
parent cdfb9a1a
...@@ -12,43 +12,17 @@ ...@@ -12,43 +12,17 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "components/browser_sync/profile_sync_service.h" #include "components/browser_sync/profile_sync_service.h"
#include "components/password_manager/core/browser/password_manager_constants.h" #include "components/password_manager/core/browser/password_manager_constants.h"
#include "components/password_manager/core/browser/password_ui_utils.h" #include "components/password_manager/core/browser/password_ui_utils.h"
#include "jni/PasswordUIView_jni.h" #include "jni/PasswordUIView_jni.h"
#include "ui/base/l10n/l10n_util.h"
using base::android::ConvertUTF16ToJavaString; using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString; using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef; using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef; using base::android::ScopedJavaLocalRef;
namespace {
// Returns the human readable version of the origin string displayed in
// Chrome settings for |form|.
std::string GetDisplayOriginForSettings(const autofill::PasswordForm& form) {
bool is_android_uri = false;
bool is_clickable = false;
GURL link_url; // TODO(crbug.com/617094) Also display link_url.
std::string human_readable_origin =
password_manager::GetShownOriginAndLinkUrl(form, &is_android_uri,
&link_url,
&is_clickable);
if (!is_clickable) {
DCHECK(is_android_uri);
human_readable_origin = password_manager::StripAndroidAndReverse(
human_readable_origin);
human_readable_origin = human_readable_origin +
l10n_util::GetStringUTF8(IDS_PASSWORDS_ANDROID_URI_SUFFIX);
}
return human_readable_origin;
}
} // namespace
PasswordUIViewAndroid::PasswordUIViewAndroid(JNIEnv* env, jobject obj) PasswordUIViewAndroid::PasswordUIViewAndroid(JNIEnv* env, jobject obj)
: password_manager_presenter_(this), weak_java_ui_controller_(env, obj) {} : password_manager_presenter_(this), weak_java_ui_controller_(env, obj) {}
...@@ -108,9 +82,10 @@ ScopedJavaLocalRef<jobject> PasswordUIViewAndroid::GetSavedPasswordEntry( ...@@ -108,9 +82,10 @@ ScopedJavaLocalRef<jobject> PasswordUIViewAndroid::GetSavedPasswordEntry(
ConvertUTF16ToJavaString(env, base::string16()), ConvertUTF16ToJavaString(env, base::string16()),
ConvertUTF16ToJavaString(env, base::string16())); ConvertUTF16ToJavaString(env, base::string16()));
} }
std::string human_readable_origin = GetDisplayOriginForSettings(*form);
return Java_PasswordUIView_createSavedPasswordEntry( return Java_PasswordUIView_createSavedPasswordEntry(
env, ConvertUTF8ToJavaString(env, human_readable_origin), env,
ConvertUTF8ToJavaString(
env, password_manager::GetShownOriginAndLinkUrl(*form).first),
ConvertUTF16ToJavaString(env, form->username_value), ConvertUTF16ToJavaString(env, form->username_value),
ConvertUTF16ToJavaString(env, form->password_value)); ConvertUTF16ToJavaString(env, form->password_value));
} }
...@@ -123,8 +98,8 @@ ScopedJavaLocalRef<jstring> PasswordUIViewAndroid::GetSavedPasswordException( ...@@ -123,8 +98,8 @@ ScopedJavaLocalRef<jstring> PasswordUIViewAndroid::GetSavedPasswordException(
password_manager_presenter_.GetPasswordException(index); password_manager_presenter_.GetPasswordException(index);
if (!form) if (!form)
return ConvertUTF8ToJavaString(env, std::string()); return ConvertUTF8ToJavaString(env, std::string());
std::string human_readable_origin = GetDisplayOriginForSettings(*form); return ConvertUTF8ToJavaString(
return ConvertUTF8ToJavaString(env, human_readable_origin); env, password_manager::GetShownOriginAndLinkUrl(*form).first);
} }
void PasswordUIViewAndroid::HandleRemoveSavedPasswordEntry( void PasswordUIViewAndroid::HandleRemoveSavedPasswordEntry(
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <stddef.h> #include <stddef.h>
#include <memory>
#include <string>
#include <vector> #include <vector>
#include "base/android/jni_weak_ref.h" #include "base/android/jni_weak_ref.h"
......
...@@ -4,59 +4,22 @@ ...@@ -4,59 +4,22 @@
#include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h"
#include "base/strings/string_util.h" #include <tuple>
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_ui_utils.h" #include "components/password_manager/core/browser/password_ui_utils.h"
#include "ui/base/l10n/l10n_util.h" #include "url/gurl.h"
namespace {
constexpr char kAndroidAppScheme[] = "android://";
constexpr char kPlayStoreAppPrefix[] =
"https://play.google.com/store/apps/details?id=";
} // namespace
namespace extensions { namespace extensions {
api::passwords_private::UrlCollection CreateUrlCollectionFromForm( api::passwords_private::UrlCollection CreateUrlCollectionFromForm(
const autofill::PasswordForm& form) { const autofill::PasswordForm& form) {
bool is_android_uri = false;
GURL link_url;
bool origin_is_clickable = false;
api::passwords_private::UrlCollection urls; api::passwords_private::UrlCollection urls;
GURL link_url;
std::tie(urls.shown, link_url) =
password_manager::GetShownOriginAndLinkUrl(form);
urls.origin = form.signon_realm; urls.origin = form.signon_realm;
urls.shown = password_manager::GetShownOriginAndLinkUrl(
form, &is_android_uri, &link_url, &origin_is_clickable);
urls.link = link_url.spec(); urls.link = link_url.spec();
if (is_android_uri) {
if (!origin_is_clickable) {
// If the origin is not clickable, link to the PlayStore. Use that
// |urls.shown| is the result of |GetHumanReadableOriginForAndroidUri| and
// does not contain the base64 hash anymore.
urls.link = urls.shown;
// e.g. android://com.example.r => r.example.com.
urls.shown = password_manager::StripAndroidAndReverse(urls.shown);
// Turn human unfriendly string into a clickable link to the PlayStore.
base::ReplaceFirstSubstringAfterOffset(&urls.link, 0, kAndroidAppScheme,
kPlayStoreAppPrefix);
}
// Currently we use "direction=rtl" in CSS to elide long origins from the
// left. This does not play nice with appending strings that end in
// punctuation symbols, which is why the bidirectional override tag is
// necessary.
// TODO(crbug.com/679434): Clean this up.
// Reference:
// https://www.w3.org/International/questions/qa-bidi-unicode-controls
urls.shown += "\u202D" + // equivalent to <bdo dir = "ltr">
l10n_util::GetStringUTF8(IDS_PASSWORDS_ANDROID_URI_SUFFIX) +
"\u202C"; // equivalent to </bdo>
}
return urls; return urls;
} }
......
...@@ -15,9 +15,7 @@ namespace extensions { ...@@ -15,9 +15,7 @@ namespace extensions {
// Obtains a collection of URLs from the passed in form. This includes an origin // Obtains a collection of URLs from the passed in form. This includes an origin
// URL used for internal logic, a human friendly string shown to the user as // URL used for internal logic, a human friendly string shown to the user as
// well as a URL that is linked to. In case the passed in form is an Android // well as a URL that is linked to.
// credential, the link URL is an affiliated website if possible, otherwise it
// links to the PlayStore.
api::passwords_private::UrlCollection CreateUrlCollectionFromForm( api::passwords_private::UrlCollection CreateUrlCollectionFromForm(
const autofill::PasswordForm& form); const autofill::PasswordForm& form);
} }
......
...@@ -4,12 +4,9 @@ ...@@ -4,12 +4,9 @@
#include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h" #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace extensions { namespace extensions {
...@@ -39,33 +36,30 @@ TEST(CreateUrlCollectionFromFormTest, UrlsFromFederatedForm) { ...@@ -39,33 +36,30 @@ TEST(CreateUrlCollectionFromFormTest, UrlsFromFederatedForm) {
EXPECT_EQ(federated_urls.link, "https://example.com/"); EXPECT_EQ(federated_urls.link, "https://example.com/");
} }
TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithoutAffiliation) { TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithoutDisplayName) {
autofill::PasswordForm android_form; autofill::PasswordForm android_form;
android_form.signon_realm = "android://example_hash@com.example.android"; android_form.signon_realm = "android://example@com.example.android";
android_form.app_display_name.clear();
api::passwords_private::UrlCollection android_urls = api::passwords_private::UrlCollection android_urls =
CreateUrlCollectionFromForm(android_form); CreateUrlCollectionFromForm(android_form);
EXPECT_EQ(android_urls.origin, "android://example_hash@com.example.android"); EXPECT_EQ("android://example@com.example.android", android_urls.origin);
EXPECT_THAT(android_urls.shown, testing::StartsWith("android.example.com")); EXPECT_EQ("android.example.com", android_urls.shown);
EXPECT_THAT(android_urls.shown, testing::HasSubstr(l10n_util::GetStringUTF8( EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android",
IDS_PASSWORDS_ANDROID_URI_SUFFIX))); android_urls.link);
EXPECT_EQ(
android_urls.link,
"https://play.google.com/store/apps/details?id=com.example.android");
} }
TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithAffiliation) { TEST(CreateUrlCollectionFromFormTest, UrlsFromAndroidFormWithAppName) {
autofill::PasswordForm android_form; autofill::PasswordForm android_form;
android_form.affiliated_web_realm = "https://example.com"; android_form.signon_realm = "android://hash@com.example.android";
android_form.signon_realm = "android://example_hash@com.example.android"; android_form.app_display_name = "Example Android App";
api::passwords_private::UrlCollection android_urls = api::passwords_private::UrlCollection android_urls =
CreateUrlCollectionFromForm(android_form); CreateUrlCollectionFromForm(android_form);
EXPECT_EQ(android_urls.origin, "android://example_hash@com.example.android"); EXPECT_EQ(android_urls.origin, "android://hash@com.example.android");
EXPECT_THAT(android_urls.shown, testing::StartsWith("example.com")); EXPECT_EQ("Example Android App", android_urls.shown);
EXPECT_THAT(android_urls.shown, testing::HasSubstr(l10n_util::GetStringUTF8( EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android",
IDS_PASSWORDS_ANDROID_URI_SUFFIX))); android_urls.link);
EXPECT_EQ(android_urls.link, "https://example.com/");
} }
} // namespace extensions } // namespace extensions
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "chrome/browser/ui/passwords/password_manager_presenter.h" #include "chrome/browser/ui/passwords/password_manager_presenter.h"
#include <algorithm> #include <algorithm>
#include <tuple>
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/gurl.h"
#if defined(OS_WIN) #if defined(OS_WIN)
#include "chrome/browser/password_manager/password_manager_util_win.h" #include "chrome/browser/password_manager/password_manager_util_win.h"
...@@ -64,39 +66,46 @@ const char kSortKeyPartsSeparator = ' '; ...@@ -64,39 +66,46 @@ const char kSortKeyPartsSeparator = ' ';
// this character should be alphabetically smaller than real federations. // this character should be alphabetically smaller than real federations.
const char kSortKeyNoFederationSymbol = '-'; const char kSortKeyNoFederationSymbol = '-';
// Helper function that returns the type of the entry (non-Android credentials,
// Android w/ affiliated web realm (i.e. clickable) or w/o web realm).
std::string GetEntryTypeCode(bool is_android_uri, bool is_clickable) {
if (!is_android_uri)
return "0";
if (is_clickable)
return "1";
return "2";
}
// Creates key for sorting password or password exception entries. The key is // Creates key for sorting password or password exception entries. The key is
// eTLD+1 followed by the reversed list of domains (e.g. // eTLD+1 followed by the reversed list of domains (e.g.
// secure.accounts.example.com => example.com.com.example.accounts.secure) and // secure.accounts.example.com => example.com.com.example.accounts.secure) and
// the scheme. If |entry_type == SAVED|, username, password and federation are // the scheme. If |entry_type == SAVED|, username, password and federation are
// appended to the key. The entry type code (non-Android, Android w/ or w/o // appended to the key. For Android credentials the canocial spec is included.
// affiliated web realm) is also appended to the key.
std::string CreateSortKey(const autofill::PasswordForm& form, std::string CreateSortKey(const autofill::PasswordForm& form,
PasswordEntryType entry_type) { PasswordEntryType entry_type) {
bool is_android_uri = false; std::string shown_origin;
bool is_clickable = false;
GURL link_url; GURL link_url;
std::string origin = password_manager::GetShownOriginAndLinkUrl( std::tie(shown_origin, link_url) =
form, &is_android_uri, &link_url, &is_clickable); password_manager::GetShownOriginAndLinkUrl(form);
if (!is_clickable) // e.g. android://com.example.r => r.example.com. const auto facet_uri =
origin = password_manager::StripAndroidAndReverse(origin); password_manager::FacetURI::FromPotentiallyInvalidSpec(form.signon_realm);
const bool is_android_uri = facet_uri.IsValidAndroidFacetURI();
if (is_android_uri) {
// In case of Android credentials |GetShownOriginAndLinkURl| might return
// the app display name, e.g. the Play Store name of the given application.
// This might or might not correspond to the eTLD+1, which is why
// |shown_origin| is set to the reversed android package name in this case,
// e.g. com.example.android => android.example.com.
shown_origin = password_manager::SplitByDotAndReverse(
facet_uri.android_package_name());
}
std::string site_name = std::string site_name =
net::registry_controlled_domains::GetDomainAndRegistry( net::registry_controlled_domains::GetDomainAndRegistry(
origin, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); shown_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (site_name.empty()) // e.g. localhost. if (site_name.empty()) // e.g. localhost.
site_name = origin; site_name = shown_origin;
std::string key = site_name + password_manager::SplitByDotAndReverse(origin);
std::string key = site_name + kSortKeyPartsSeparator;
// Since multiple distinct credentials might have the same site name, more
// information is added. For Android credentials this includes the full
// canonical spec which is guaranteed to be unique for a given App.
key += is_android_uri ? facet_uri.canonical_spec()
: password_manager::SplitByDotAndReverse(shown_origin);
if (entry_type == PasswordEntryType::SAVED) { if (entry_type == PasswordEntryType::SAVED) {
key += kSortKeyPartsSeparator + base::UTF16ToUTF8(form.username_value) + key += kSortKeyPartsSeparator + base::UTF16ToUTF8(form.username_value) +
...@@ -110,13 +119,7 @@ std::string CreateSortKey(const autofill::PasswordForm& form, ...@@ -110,13 +119,7 @@ std::string CreateSortKey(const autofill::PasswordForm& form,
} }
// To separate HTTP/HTTPS credentials, add the scheme to the key. // To separate HTTP/HTTPS credentials, add the scheme to the key.
key += kSortKeyPartsSeparator + link_url.scheme(); return key += kSortKeyPartsSeparator + link_url.scheme();
// Since Android and non-Android entries shouldn't be merged into one entry,
// add the entry type code to the sort key.
key +=
kSortKeyPartsSeparator + GetEntryTypeCode(is_android_uri, is_clickable);
return key;
} }
// Finds duplicates of |form| in |duplicates|, removes them from |store| and // Finds duplicates of |form| in |duplicates|, removes them from |store| and
......
...@@ -29,7 +29,7 @@ struct SortEntry { ...@@ -29,7 +29,7 @@ struct SortEntry {
const char* const origin; const char* const origin;
const char* const username; const char* const username;
const char* const password; const char* const password;
const char* const affiliated_web_realm; const char* const app_display_name;
const char* const federation; const char* const federation;
const int expected_position; const int expected_position;
}; };
...@@ -144,8 +144,8 @@ void PasswordManagerPresenterTest::SortAndCheckPositions( ...@@ -144,8 +144,8 @@ void PasswordManagerPresenterTest::SortAndCheckPositions(
if (entry.federation != nullptr) if (entry.federation != nullptr)
form->federation_origin = url::Origin(GURL(entry.federation)); form->federation_origin = url::Origin(GURL(entry.federation));
} }
if (entry.affiliated_web_realm) if (entry.app_display_name)
form->affiliated_web_realm = entry.affiliated_web_realm; form->app_display_name = entry.app_display_name;
list.push_back(std::move(form)); list.push_back(std::move(form));
if (entry.expected_position >= 0) if (entry.expected_position >= 0)
expected_number_of_unique_entries++; expected_number_of_unique_entries++;
...@@ -303,16 +303,21 @@ TEST_F(PasswordManagerPresenterTest, Sorting_PasswordExceptions) { ...@@ -303,16 +303,21 @@ TEST_F(PasswordManagerPresenterTest, Sorting_PasswordExceptions) {
TEST_F(PasswordManagerPresenterTest, Sorting_AndroidCredentials) { TEST_F(PasswordManagerPresenterTest, Sorting_AndroidCredentials) {
const SortEntry test_cases[] = { const SortEntry test_cases[] = {
{"https://alpha.com", "user", "secret", nullptr, nullptr, 1}, // Regular Web Credential.
{"android://hash@com.alpha", "user", "secret", "https://alpha.com", {"https://alpha.example.com", "user", "secret", nullptr, nullptr, 3},
nullptr, 0}, // First Android Credential.
{"android://hash@com.alpha", "user", "secret", "https://alpha.com", {"android://hash@com.example.alpha", "user", "secret", nullptr, nullptr,
0},
// App display name is irrelevant, this should be hidden as a duplicate
// of the first one.
{"android://hash@com.example.alpha", "user", "secret", "Example App",
nullptr, -1}, nullptr, -1},
{"android://hash@com.alpha", "user", "secret", nullptr, nullptr, 2}, // Apps with different package names are distinct.
{"android://hash@com.betta.android", "user", "secret", {"android://hash@com.example.beta", "user", "secret", nullptr, nullptr,
"https://betta.com", nullptr, 3}, 1},
{"android://hash@com.betta.android", "user", "secret", nullptr, nullptr, // Apps with same package name but different hashes are distinct.
4}}; {"android://hash_different@com.example.alpha", "user", "secret", nullptr,
nullptr, 2}};
SortAndCheckPositions(test_cases, arraysize(test_cases), SortAndCheckPositions(test_cases, arraysize(test_cases),
PasswordEntryType::SAVED); PasswordEntryType::SAVED);
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "chrome/browser/ui/webui/options/password_manager_handler.h" #include "chrome/browser/ui/webui/options/password_manager_handler.h"
#include <memory> #include <memory>
#include <tuple>
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
...@@ -29,6 +30,7 @@ ...@@ -29,6 +30,7 @@
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "components/browser_sync/profile_sync_service.h" #include "components/browser_sync/profile_sync_service.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/export/password_exporter.h" #include "components/password_manager/core/browser/export/password_exporter.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h" #include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_manager_constants.h" #include "components/password_manager/core/browser/password_manager_constants.h"
...@@ -46,6 +48,7 @@ ...@@ -46,6 +48,7 @@
#include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui.h"
#include "content/public/common/origin_util.h" #include "content/public/common/origin_util.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace options { namespace options {
...@@ -65,20 +68,20 @@ const char kFederationField[] = "federation"; ...@@ -65,20 +68,20 @@ const char kFederationField[] = "federation";
// Android URI, and whether the origin is secure. // Android URI, and whether the origin is secure.
void CopyOriginInfoOfPasswordForm(const autofill::PasswordForm& form, void CopyOriginInfoOfPasswordForm(const autofill::PasswordForm& form,
base::DictionaryValue* entry) { base::DictionaryValue* entry) {
bool is_android_uri = false; std::string shown_origin;
bool origin_is_clickable = false;
GURL link_url; GURL link_url;
entry->SetString( std::tie(shown_origin, link_url) =
kShownOriginField, password_manager::GetShownOriginAndLinkUrl(form);
password_manager::GetShownOriginAndLinkUrl( entry->SetString(kShownOriginField, shown_origin);
form, &is_android_uri, &link_url, &origin_is_clickable));
DCHECK(link_url.is_valid()); DCHECK(link_url.is_valid());
entry->SetString( entry->SetString(kUrlField,
kUrlField, url_formatter::FormatUrl( url_formatter::FormatUrl(
link_url, url_formatter::kFormatUrlOmitNothing, link_url, url_formatter::kFormatUrlOmitNothing,
net::UnescapeRule::SPACES, nullptr, nullptr, nullptr)); net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
entry->SetBoolean(kIsAndroidUriField, is_android_uri); entry->SetBoolean(
entry->SetBoolean(kIsClickable, origin_is_clickable); kIsAndroidUriField,
password_manager::IsValidAndroidFacetURI(form.signon_realm));
entry->SetBoolean(kIsClickable, true);
entry->SetBoolean(kIsSecureField, content::IsOriginSecure(link_url)); entry->SetBoolean(kIsSecureField, content::IsOriginSecure(link_url));
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <vector>
#include "base/strings/string_split.h" #include "base/strings/string_split.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
...@@ -21,6 +22,9 @@ namespace { ...@@ -21,6 +22,9 @@ namespace {
// The URL prefixes that are removed from shown origin. // The URL prefixes that are removed from shown origin.
const char* const kRemovedPrefixes[] = {"m.", "mobile.", "www."}; const char* const kRemovedPrefixes[] = {"m.", "mobile.", "www."};
constexpr char kPlayStoreAppPrefix[] =
"https://play.google.com/store/apps/details?id=";
} // namespace } // namespace
std::string SplitByDotAndReverse(base::StringPiece host) { std::string SplitByDotAndReverse(base::StringPiece host) {
...@@ -30,41 +34,24 @@ std::string SplitByDotAndReverse(base::StringPiece host) { ...@@ -30,41 +34,24 @@ std::string SplitByDotAndReverse(base::StringPiece host) {
return base::JoinString(parts, "."); return base::JoinString(parts, ".");
} }
std::string StripAndroidAndReverse(const std::string& origin) { std::pair<std::string, GURL> GetShownOriginAndLinkUrl(
const int kAndroidAppSchemeAndDelimiterLength = sizeof("android://") - 1; const autofill::PasswordForm& password_form) {
return SplitByDotAndReverse( std::string shown_origin;
base::StringPiece(&origin[kAndroidAppSchemeAndDelimiterLength], GURL link_url;
origin.length() - kAndroidAppSchemeAndDelimiterLength));
}
std::string GetShownOriginAndLinkUrl(
const autofill::PasswordForm& password_form,
bool* is_android_uri,
GURL* link_url,
bool* origin_is_clickable) {
DCHECK(is_android_uri);
DCHECK(origin_is_clickable);
DCHECK(link_url);
password_manager::FacetURI facet_uri = FacetURI facet_uri =
password_manager::FacetURI::FromPotentiallyInvalidSpec( FacetURI::FromPotentiallyInvalidSpec(password_form.signon_realm);
password_form.signon_realm); if (facet_uri.IsValidAndroidFacetURI()) {
*is_android_uri = facet_uri.IsValidAndroidFacetURI(); shown_origin = password_form.app_display_name.empty()
if (*is_android_uri) { ? SplitByDotAndReverse(facet_uri.android_package_name())
if (password_form.affiliated_web_realm.empty()) { : password_form.app_display_name;
*origin_is_clickable = false; link_url = GURL(kPlayStoreAppPrefix + facet_uri.android_package_name());
// Since the full url should be shown in the tooltip even for } else {
// non-clickable origins, return it as |link_url|. shown_origin = GetShownOrigin(password_form.origin);
*link_url = GURL(password_form.signon_realm); link_url = password_form.origin;
return GetHumanReadableOriginForAndroidUri(facet_uri);
}
*origin_is_clickable = true;
*link_url = GURL(password_form.affiliated_web_realm);
return GetShownOrigin(*link_url);
} }
*origin_is_clickable = true;
*link_url = password_form.origin; return {std::move(shown_origin), std::move(link_url)};
return GetShownOrigin(password_form.origin);
} }
std::string GetShownOrigin(const GURL& origin) { std::string GetShownOrigin(const GURL& origin) {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_UI_UTILS_H_ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_UI_UTILS_H_
#include <string> #include <string>
#include <utility>
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
...@@ -22,25 +23,18 @@ namespace password_manager { ...@@ -22,25 +23,18 @@ namespace password_manager {
// Reverses order of labels in hostname. // Reverses order of labels in hostname.
std::string SplitByDotAndReverse(base::StringPiece host); std::string SplitByDotAndReverse(base::StringPiece host);
// Removes 'android://' and reverses order of labels in hostname. // Returns a human readable origin and a link URL for the provided
std::string StripAndroidAndReverse(const std::string& origin); // |password_form|.
//
// Returns a string suitable for security display to the user (just like // For Web credentials the returned origin is suitable for security display and
// |FormatUrlForSecurityDisplay| with OMIT_HTTP_AND_HTTPS) based on origin of // is stripped off common prefixes like "m.", "mobile." or "www.". Furthermore
// |password_form| and without prefixes "m.", "mobile." or "www.". Also // the link URL is set to the full origin of the original form.
// returns the full URL of the origin as |link_url|. |link_url| will be also //
// shown as tooltip on the password page. // For Android credentials the returned origin is set to the Play Store name
// For Android forms with empty |password_form.affiliated_web_realm|, // if available, otherwise it is the reversed package name (e.g.
// returns the result of GetHumanReadableOriginForAndroidUri. For other Android // com.example.android gets transformed to android.example.com).
// forms, returns |password_form.affiliated_web_realm|. std::pair<std::string, GURL> GetShownOriginAndLinkUrl(
// |*origin_is_clickable| is set to true, except for Android forms with empty const autofill::PasswordForm& password_form);
// |password_form.affiliated_web_realm|.
// |is_android_url|, |link_url|, |origin_is_clickable| are required to non-null.
std::string GetShownOriginAndLinkUrl(
const autofill::PasswordForm& password_form,
bool* is_android_uri,
GURL* link_url,
bool* origin_is_clickable);
// Returns a string suitable for security display to the user (just like // Returns a string suitable for security display to the user (just like
// |FormatUrlForSecurityDisplay| with OMIT_HTTP_AND_HTTPS) based on origin of // |FormatUrlForSecurityDisplay| with OMIT_HTTP_AND_HTTPS) based on origin of
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_ui_utils.h" #include "components/password_manager/core/browser/password_ui_utils.h"
#include <tuple>
#include "components/autofill/core/common/password_form.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace password_manager { namespace password_manager {
...@@ -37,44 +40,32 @@ TEST(GetShownOriginTest, RemovePrefixes) { ...@@ -37,44 +40,32 @@ TEST(GetShownOriginTest, RemovePrefixes) {
} }
} }
TEST(GetShownOriginAndLinkUrlTest, OriginFromAndroidForm_NoAffiliatedRealm) { TEST(GetShownOriginAndLinkUrlTest, OriginFromAndroidForm_NoAppDisplayName) {
autofill::PasswordForm android_form; autofill::PasswordForm android_form;
android_form.signon_realm = android_form.signon_realm = "android://hash@com.example.android";
"android://" android_form.app_display_name.clear();
"m3HSJL1i83hdltRq0-o9czGb-8KJDKra4t_"
"3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==" std::string shown_origin;
"@com.example.android";
android_form.affiliated_web_realm = std::string();
bool is_android_uri;
GURL link_url; GURL link_url;
bool origin_is_clickable; std::tie(shown_origin, link_url) = GetShownOriginAndLinkUrl(android_form);
EXPECT_EQ("android://com.example.android",
GetShownOriginAndLinkUrl(android_form, &is_android_uri, &link_url, EXPECT_EQ("android.example.com", shown_origin);
&origin_is_clickable)); EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android",
EXPECT_TRUE(is_android_uri); link_url.spec());
EXPECT_FALSE(origin_is_clickable);
EXPECT_EQ(GURL(android_form.signon_realm), link_url);
} }
TEST(GetShownOriginAndLinkUrlTest, OriginFromAndroidForm_WithAffiliatedRealm) { TEST(GetShownOriginAndLinkUrlTest, OriginFromAndroidForm_WithAppDisplayName) {
autofill::PasswordForm android_form; autofill::PasswordForm android_form;
android_form.signon_realm = android_form.signon_realm = "android://hash@com.example.android";
"android://" android_form.app_display_name = "Example Android App";
"m3HSJL1i83hdltRq0-o9czGb-8KJDKra4t_"
"3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==" std::string shown_origin;
"@com.example.android";
android_form.affiliated_web_realm = "https://example.com/";
bool is_android_uri;
GURL link_url; GURL link_url;
bool origin_is_clickable; std::tie(shown_origin, link_url) = GetShownOriginAndLinkUrl(android_form);
EXPECT_EQ("example.com",
GetShownOriginAndLinkUrl(android_form, &is_android_uri, &link_url, EXPECT_EQ("Example Android App", shown_origin);
&origin_is_clickable)); EXPECT_EQ("https://play.google.com/store/apps/details?id=com.example.android",
EXPECT_TRUE(is_android_uri); link_url.spec());
EXPECT_TRUE(origin_is_clickable);
EXPECT_EQ(GURL(android_form.affiliated_web_realm), link_url);
} }
TEST(GetShownOriginAndLinkUrlTest, OriginFromNonAndroidForm) { TEST(GetShownOriginAndLinkUrlTest, OriginFromNonAndroidForm) {
...@@ -82,15 +73,12 @@ TEST(GetShownOriginAndLinkUrlTest, OriginFromNonAndroidForm) { ...@@ -82,15 +73,12 @@ TEST(GetShownOriginAndLinkUrlTest, OriginFromNonAndroidForm) {
form.signon_realm = "https://example.com/"; form.signon_realm = "https://example.com/";
form.origin = GURL("https://example.com/login?ref=1"); form.origin = GURL("https://example.com/login?ref=1");
bool is_android_uri; std::string shown_origin;
GURL link_url; GURL link_url;
bool origin_is_clickable; std::tie(shown_origin, link_url) = GetShownOriginAndLinkUrl(form);
EXPECT_EQ("example.com",
GetShownOriginAndLinkUrl(form, &is_android_uri, &link_url, EXPECT_EQ("example.com", shown_origin);
&origin_is_clickable)); EXPECT_EQ(GURL("https://example.com/login?ref=1"), link_url);
EXPECT_FALSE(is_android_uri);
EXPECT_TRUE(origin_is_clickable);
EXPECT_EQ(form.origin, link_url);
} }
TEST(SplitByDotAndReverseTest, ReversedHostname) { TEST(SplitByDotAndReverseTest, ReversedHostname) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment