Commit 7020c4e0 authored by Vaclav Brozek's avatar Vaclav Brozek Committed by Commit Bot

Support federated credentials in password settings on iOS

The current implementation of the password detail view in iOS settings tries to
display the password value, which federated credentials have none. Instead,
they have a web origin identifying the federated identity provider associated
with the stored credential. This origin is currently not displayed.

Therefore this CL changes the password detail view for federated credentials by
replacing the password section with a federation section. The federation origin
is very likely to be just a short hostname which fits on one line so there is
no copy button for it as in the case of the other meta data.

Desktop settings already do something similar: they replace the password field
with a "with <federation origin>" string.

Screenshot at https://crbug.com/740384#c2.

Bug: 740384
Change-Id: Ibf4ff13a864fab450b3558246d8b26e04182b804
Reviewed-on: https://chromium-review.googlesource.com/563623
Commit-Queue: Vaclav Brozek <vabr@chromium.org>
Reviewed-by: default avatarLouis Romero <lpromero@chromium.org>
Cr-Commit-Position: refs/heads/master@{#485410}
parent facc6aef
...@@ -1236,6 +1236,9 @@ Handoff must also be enabled in the General section of Settings, and your device ...@@ -1236,6 +1236,9 @@ Handoff must also be enabled in the General section of Settings, and your device
<message name="IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME" desc="Label indicating that the text displayed below is the username to which a saved password corresponds. The password is displayed in the same view, but under a diferent label. [Length: 20em] [iOS only]"> <message name="IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME" desc="Label indicating that the text displayed below is the username to which a saved password corresponds. The password is displayed in the same view, but under a diferent label. [Length: 20em] [iOS only]">
Username Username
</message> </message>
<message name="IDS_IOS_SHOW_PASSWORD_VIEW_FEDERATION" desc="Label indicating that the text displayed below is the hostname of the identity provider used for the displayed credential. [Length: to fit on one line] [iOS only]">
With Federation
</message>
<message name="IDS_IOS_CANCEL_PASSWORD_DELETION" desc="Label of a confirmation dialogue button which allows the user to cancel deletion of a stored password. [Length: one line] [iOS only]"> <message name="IDS_IOS_CANCEL_PASSWORD_DELETION" desc="Label of a confirmation dialogue button which allows the user to cancel deletion of a stored password. [Length: one line] [iOS only]">
Cancel Cancel
</message> </message>
......
...@@ -39,6 +39,7 @@ typedef NS_ENUM(NSInteger, SectionIdentifier) { ...@@ -39,6 +39,7 @@ typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierSite = kSectionIdentifierEnumZero, SectionIdentifierSite = kSectionIdentifierEnumZero,
SectionIdentifierUsername, SectionIdentifierUsername,
SectionIdentifierPassword, SectionIdentifierPassword,
SectionIdentifierFederation,
SectionIdentifierDelete, SectionIdentifierDelete,
}; };
...@@ -51,6 +52,7 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -51,6 +52,7 @@ typedef NS_ENUM(NSInteger, ItemType) {
ItemTypePassword, ItemTypePassword,
ItemTypeCopyPassword, ItemTypeCopyPassword,
ItemTypeShowHide, ItemTypeShowHide,
ItemTypeFederation,
ItemTypeDelete, ItemTypeDelete,
}; };
...@@ -72,6 +74,8 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -72,6 +74,8 @@ typedef NS_ENUM(NSInteger, ItemType) {
NSString* _username; NSString* _username;
// The saved password. // The saved password.
NSString* _password; NSString* _password;
// The federation providing this credential, if any.
NSString* _federation;
// The origin site of the saved credential. // The origin site of the saved credential.
NSString* _site; NSString* _site;
// Whether the password is shown in plain text form or in obscured form. // Whether the password is shown in plain text form or in obscured form.
...@@ -115,7 +119,12 @@ reauthenticationModule:(id<ReauthenticationProtocol>)reauthenticationModule { ...@@ -115,7 +119,12 @@ reauthenticationModule:(id<ReauthenticationProtocol>)reauthenticationModule {
_passwordForm = passwordForm; _passwordForm = passwordForm;
if (!_passwordForm.blacklisted_by_user) { if (!_passwordForm.blacklisted_by_user) {
_username = base::SysUTF16ToNSString(_passwordForm.username_value); _username = base::SysUTF16ToNSString(_passwordForm.username_value);
_password = base::SysUTF16ToNSString(_passwordForm.password_value); if (_passwordForm.federation_origin.unique()) {
_password = base::SysUTF16ToNSString(_passwordForm.password_value);
} else {
_federation =
base::SysUTF8ToNSString(_passwordForm.federation_origin.host());
}
} }
_site = base::SysUTF8ToNSString(_passwordForm.origin.spec()); _site = base::SysUTF8ToNSString(_passwordForm.origin.spec());
self.title = [PasswordDetailsCollectionViewController self.title = [PasswordDetailsCollectionViewController
...@@ -185,24 +194,42 @@ reauthenticationModule:(id<ReauthenticationProtocol>)reauthenticationModule { ...@@ -185,24 +194,42 @@ reauthenticationModule:(id<ReauthenticationProtocol>)reauthenticationModule {
[model addItem:[self usernameCopyButtonItem] [model addItem:[self usernameCopyButtonItem]
toSectionWithIdentifier:SectionIdentifierUsername]; toSectionWithIdentifier:SectionIdentifierUsername];
[model addSectionWithIdentifier:SectionIdentifierPassword]; if (_passwordForm.federation_origin.unique()) {
CollectionViewTextItem* passwordHeader = [model addSectionWithIdentifier:SectionIdentifierPassword];
[[CollectionViewTextItem alloc] initWithType:ItemTypeHeader]; CollectionViewTextItem* passwordHeader =
passwordHeader.text = [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader];
l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_PASSWORD); passwordHeader.text =
passwordHeader.textColor = [[MDCPalette greyPalette] tint500]; l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_PASSWORD);
[model setHeader:passwordHeader passwordHeader.textColor = [[MDCPalette greyPalette] tint500];
forSectionWithIdentifier:SectionIdentifierPassword]; [model setHeader:passwordHeader
_passwordItem = [[PasswordDetailsItem alloc] initWithType:ItemTypePassword]; forSectionWithIdentifier:SectionIdentifierPassword];
_passwordItem.text = _password; _passwordItem =
_passwordItem.showingText = NO; [[PasswordDetailsItem alloc] initWithType:ItemTypePassword];
[model addItem:_passwordItem _passwordItem.text = _password;
toSectionWithIdentifier:SectionIdentifierPassword]; _passwordItem.showingText = NO;
[model addItem:_passwordItem
[model addItem:[self passwordCopyButtonItem] toSectionWithIdentifier:SectionIdentifierPassword];
toSectionWithIdentifier:SectionIdentifierPassword];
[model addItem:[self showHidePasswordButtonItem] [model addItem:[self passwordCopyButtonItem]
toSectionWithIdentifier:SectionIdentifierPassword]; toSectionWithIdentifier:SectionIdentifierPassword];
[model addItem:[self showHidePasswordButtonItem]
toSectionWithIdentifier:SectionIdentifierPassword];
} else {
[model addSectionWithIdentifier:SectionIdentifierFederation];
CollectionViewTextItem* federationHeader =
[[CollectionViewTextItem alloc] initWithType:ItemTypeHeader];
federationHeader.text =
l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_FEDERATION);
federationHeader.textColor = [[MDCPalette greyPalette] tint500];
[model setHeader:federationHeader
forSectionWithIdentifier:SectionIdentifierFederation];
PasswordDetailsItem* federationItem =
[[PasswordDetailsItem alloc] initWithType:ItemTypeFederation];
federationItem.text = _federation;
federationItem.showingText = YES;
[model addItem:federationItem
toSectionWithIdentifier:SectionIdentifierFederation];
}
} }
[model addSectionWithIdentifier:SectionIdentifierDelete]; [model addSectionWithIdentifier:SectionIdentifierDelete];
......
...@@ -190,6 +190,62 @@ TEST_F(PasswordDetailsCollectionViewControllerTest, ...@@ -190,6 +190,62 @@ TEST_F(PasswordDetailsCollectionViewControllerTest,
kBlacklistedDeleteButtonItem); kBlacklistedDeleteButtonItem);
} }
TEST_F(PasswordDetailsCollectionViewControllerTest,
TestInitialization_Federated) {
constexpr int kFederatedSiteSection = 0;
constexpr int kFederatedSiteItem = 0;
constexpr int kFederatedCopySiteButtonItem = 1;
constexpr int kFederatedUsernameSection = 1;
constexpr int kFederatedUsernameItem = 0;
constexpr int kFederatedCopyUsernameButtonItem = 1;
constexpr int kFederatedFederationSection = 2;
constexpr int kFederatedFederationItem = 0;
constexpr int kFederatedDeleteSection = 3;
constexpr int kFederatedDeleteButtonItem = 0;
form_.password_value.clear();
form_.federation_origin = url::Origin(GURL("https://famous.provider.net"));
CreateController();
CheckController();
EXPECT_EQ(4, NumberOfSections());
// Site section
EXPECT_EQ(2, NumberOfItemsInSection(kFederatedSiteSection));
CheckSectionHeaderWithId(IDS_IOS_SHOW_PASSWORD_VIEW_SITE,
kFederatedSiteSection);
PasswordDetailsItem* siteItem =
GetCollectionViewItem(kFederatedSiteSection, kFederatedSiteItem);
EXPECT_NSEQ(origin_, siteItem.text);
EXPECT_TRUE(siteItem.showingText);
CheckTextCellTitleWithId(IDS_IOS_SETTINGS_SITE_COPY_BUTTON,
kFederatedSiteSection, kFederatedCopySiteButtonItem);
// Username section
EXPECT_EQ(2, NumberOfItemsInSection(kFederatedUsernameSection));
CheckSectionHeaderWithId(IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME,
kFederatedUsernameSection);
PasswordDetailsItem* usernameItem =
GetCollectionViewItem(kFederatedUsernameSection, kFederatedUsernameItem);
EXPECT_NSEQ(kUsername, usernameItem.text);
EXPECT_TRUE(usernameItem.showingText);
CheckTextCellTitleWithId(IDS_IOS_SETTINGS_USERNAME_COPY_BUTTON,
kFederatedUsernameSection,
kFederatedCopyUsernameButtonItem);
// Federated section
EXPECT_EQ(1, NumberOfItemsInSection(kFederatedFederationSection));
CheckSectionHeaderWithId(IDS_IOS_SHOW_PASSWORD_VIEW_FEDERATION,
kFederatedFederationSection);
PasswordDetailsItem* federationItem = GetCollectionViewItem(
kFederatedFederationSection, kFederatedFederationItem);
EXPECT_NSEQ(@"famous.provider.net", federationItem.text);
EXPECT_TRUE(federationItem.showingText);
// Delete section
EXPECT_EQ(1, NumberOfItemsInSection(kFederatedDeleteSection));
CheckTextCellTitleWithId(IDS_IOS_SETTINGS_PASSWORD_DELETE_BUTTON,
kFederatedDeleteSection, kFederatedDeleteButtonItem);
}
struct SimplifyOriginTestData { struct SimplifyOriginTestData {
GURL origin; GURL origin;
NSString* expectedSimplifiedOrigin; NSString* expectedSimplifiedOrigin;
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h" #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/origin.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -895,4 +896,70 @@ MockReauthenticationModule* SetUpAndReturnMockReauthenticationModule() { ...@@ -895,4 +896,70 @@ MockReauthenticationModule* SetUpAndReturnMockReauthenticationModule() {
[self clearPasswordStore]; [self clearPasswordStore];
} }
// Checks that federated credentials have no password but show the federation.
- (void)testFederated {
[self scopedEnablePasswordManagementAndViewingUI];
PasswordForm federated;
federated.username_value = base::ASCIIToUTF16("federated username");
federated.origin = GURL("https://example.com");
federated.signon_realm = federated.origin.spec();
federated.federation_origin =
url::Origin(GURL("https://famous.provider.net"));
[self savePasswordFormToStore:federated];
[self openPasswordSettings];
[[EarlGrey
selectElementWithMatcher:Entry(
@"https://example.com, federated username")]
performAction:grey_tap()];
// Check that the Site, Username, Federation and Delete Saved Password
// sections are there. (No scrolling for the first two, which should be high
// enough to always start visible.)
[[EarlGrey selectElementWithMatcher:SiteHeader()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:UsernameHeader()]
assertWithMatcher:grey_sufficientlyVisible()];
// For federation check both the section header and content.
[[[EarlGrey
selectElementWithMatcher:
grey_allOf(grey_accessibilityTrait(UIAccessibilityTraitHeader),
grey_accessibilityLabel(l10n_util::GetNSString(
IDS_IOS_SHOW_PASSWORD_VIEW_FEDERATION)),
nullptr)]
usingSearchAction:grey_scrollInDirection(kGREYDirectionDown,
kScrollAmount)
onElementWithMatcher:grey_accessibilityID(
@"PasswordDetailsCollectionViewController")]
assertWithMatcher:grey_sufficientlyVisible()];
[[[EarlGrey selectElementWithMatcher:grey_text(@"famous.provider.net")]
usingSearchAction:grey_scrollInDirection(kGREYDirectionDown,
kScrollAmount)
onElementWithMatcher:grey_accessibilityID(
@"PasswordDetailsCollectionViewController")]
assertWithMatcher:grey_sufficientlyVisible()];
// Not using DeleteButton() matcher here, because that also encodes the
// relative position against the password section, which is missing in this
// case.
[[[EarlGrey selectElementWithMatcher:
ButtonWithAccessibilityLabel(l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORD_DELETE_BUTTON))]
usingSearchAction:grey_scrollInDirection(kGREYDirectionDown,
kScrollAmount)
onElementWithMatcher:grey_accessibilityID(
@"PasswordDetailsCollectionViewController")]
assertWithMatcher:grey_sufficientlyVisible()];
// Check that the password is not present.
[[EarlGrey selectElementWithMatcher:PasswordHeader()]
assertWithMatcher:grey_nil()];
[self tapBackArrow];
[self tapBackArrow];
[self tapDone];
[self clearPasswordStore];
}
@end @end
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