[iOS] Make the gaia available to the CPE when we have no credentials
Previously, the gaia was taken from existing credentials, which works as long as there is already at least one credential in the database. If there are no credentials in the database, we need to access the current user's gaia in order to create new passwords or passkeys, so we instead use the ios keychain for that purpose. Low-Coverage-Reason: TRIVIAL_CHANGE to existing code and mock class Bug: 355042671 Change-Id: Iad8f7625695563caf82152db07181a7cbaa44f91 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5958607 Commit-Queue: Alexis Hétu <sugoi@chromium.org> Reviewed-by: Tommy Martino <tmartino@chromium.org> Cr-Commit-Position: refs/heads/main@{#1373574}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
b74efb8957
commit
7e9eefee11
ios
chrome
browser
credential_provider
credential_provider_extension
components
credential_provider_extension
@ -68,6 +68,7 @@ if (ios_enable_credential_provider_extension) {
|
||||
"//ios/chrome/common/credential_provider:ui",
|
||||
"//ios/chrome/common/ui/favicon:favicon",
|
||||
"//ios/chrome/common/ui/favicon:favicon_constants",
|
||||
"//ios/components/credential_provider_extension:password_util",
|
||||
"//ios/web/public",
|
||||
"//url",
|
||||
]
|
||||
|
@ -6,4 +6,5 @@ include_rules = [
|
||||
"+ios/chrome/browser/favicon/model",
|
||||
"+ios/chrome/browser/passwords/model",
|
||||
"+ios/chrome/browser/webauthn/model",
|
||||
"+ios/components/credential_provider_extension",
|
||||
]
|
||||
|
@ -93,6 +93,11 @@ class CredentialProviderService
|
||||
// Syncs the credential store to disk.
|
||||
void SyncStore();
|
||||
|
||||
// Saves the Gaia of the current user to the ios keychain for the Credential
|
||||
// Provider Extension to use when creating new credentials. Returns whether
|
||||
// writing the data to the ios keychain succeeded.
|
||||
bool SaveGaia();
|
||||
|
||||
// Add credentials from `forms`. Currently simply calls either the legacy or
|
||||
// refactored version of this function.
|
||||
void AddCredentials(MemoryCredentialStore* store,
|
||||
|
@ -37,6 +37,7 @@
|
||||
#import "ios/chrome/common/credential_provider/archivable_credential+passkey.h"
|
||||
#import "ios/chrome/common/credential_provider/constants.h"
|
||||
#import "ios/chrome/common/credential_provider/credential_store.h"
|
||||
#import "ios/components/credential_provider_extension/password_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -313,6 +314,13 @@ void CredentialProviderService::SyncAllCredentials(
|
||||
SyncStore();
|
||||
}
|
||||
|
||||
bool CredentialProviderService::SaveGaia() {
|
||||
CoreAccountInfo account =
|
||||
identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
|
||||
return credential_provider_extension::StoreGaiaInKeychain(
|
||||
base::SysUTF8ToNSString(account.gaia));
|
||||
}
|
||||
|
||||
void CredentialProviderService::SyncStore() {
|
||||
base::UmaHistogramBoolean(kSyncStoreHistogramName, true);
|
||||
|
||||
@ -329,6 +337,9 @@ void CredentialProviderService::SyncStore() {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
if (!SaveGaia()) {
|
||||
return;
|
||||
}
|
||||
if (weak_credential_store) {
|
||||
SyncASIdentityStore(weak_credential_store);
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ source_set("credential_provider") {
|
||||
"//ios/chrome/credential_provider_extension/ui",
|
||||
"//ios/chrome/credential_provider_extension/ui:credential_response_handler",
|
||||
"//ios/chrome/credential_provider_extension/ui:feature_flags",
|
||||
"//ios/components/credential_provider_extension:password_util",
|
||||
]
|
||||
|
||||
frameworks = [
|
||||
|
@ -34,6 +34,7 @@
|
||||
#import "ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.h"
|
||||
#import "ios/chrome/credential_provider_extension/ui/saving_enterprise_disabled_view_controller.h"
|
||||
#import "ios/chrome/credential_provider_extension/ui/stale_credentials_view_controller.h"
|
||||
#import "ios/components/credential_provider_extension/password_util.h"
|
||||
|
||||
namespace {
|
||||
UIColor* BackgroundColor() {
|
||||
@ -431,6 +432,23 @@ UIColor* BackgroundColor() {
|
||||
[self.extensionContext completeExtensionConfigurationRequest];
|
||||
}
|
||||
|
||||
- (NSString*)gaia {
|
||||
NSString* gaia = credential_provider_extension::LoadGaiaFromKeychain();
|
||||
if (gaia.length > 0) {
|
||||
return gaia;
|
||||
}
|
||||
|
||||
// As a fallback, attempt to get a valid gaia from existing credentials.
|
||||
NSArray<id<Credential>>* credentials = self.credentialStore.credentials;
|
||||
NSUInteger credentialIndex =
|
||||
[credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
|
||||
NSUInteger idx, BOOL* stop) {
|
||||
return credential.gaia.length > 0;
|
||||
}];
|
||||
return credentialIndex != NSNotFound ? credentials[credentialIndex].gaia
|
||||
: nil;
|
||||
}
|
||||
|
||||
#pragma mark - PasskeyKeychainProviderBridgeDelegate
|
||||
|
||||
- (void)showEnrollmentWelcomeScreen:(ProceduralBlock)enrollBlock {
|
||||
@ -755,21 +773,6 @@ UIColor* BackgroundColor() {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the gaia for the account used for passkey creation.
|
||||
- (NSString*)gaia {
|
||||
// TODO(crbug.com/355041765): Get gaia from ios keychain instead of the
|
||||
// credential store, since that would fail if there are no synced credentials
|
||||
// in the store.
|
||||
NSArray<id<Credential>>* credentials = self.credentialStore.credentials;
|
||||
NSUInteger credentialIndex =
|
||||
[credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
|
||||
NSUInteger idx, BOOL* stop) {
|
||||
return credential.gaia.length > 0;
|
||||
}];
|
||||
return credentialIndex != NSNotFound ? credentials[credentialIndex].gaia
|
||||
: nil;
|
||||
}
|
||||
|
||||
// Triggers the process to fetch the security domain secret and calls the
|
||||
// completion block with the security domain secret as input.
|
||||
- (void)fetchSecurityDomainSecretForGaia:(NSString*)gaia
|
||||
|
@ -39,6 +39,9 @@ typedef void (^FetchSecurityDomainSecretCompletionBlock)(
|
||||
|
||||
- (void)completeExtensionConfigurationRequest;
|
||||
|
||||
// Returns the gaia for the account used for credential creation.
|
||||
- (NSString*)gaia;
|
||||
|
||||
@end
|
||||
|
||||
#endif // IOS_CHROME_CREDENTIAL_PROVIDER_EXTENSION_UI_CREDENTIAL_RESPONSE_HANDLER_H_
|
||||
|
@ -57,4 +57,8 @@ NSData* SecurityDomainSecret() {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
- (NSString*)gaia {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -110,6 +110,10 @@ using base::SysUTF16ToNSString;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString*)gaia {
|
||||
return [self.credentialResponseHandler gaia];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
// Checks whether a credential already exists with the given username.
|
||||
|
@ -36,6 +36,9 @@
|
||||
gaia:(NSString*)gaia
|
||||
shouldReplace:(BOOL)shouldReplace;
|
||||
|
||||
// Returns the gaia for the account used for credential creation.
|
||||
- (NSString*)gaia;
|
||||
|
||||
@end
|
||||
|
||||
// View Controller where a user can create a new credential and use a suggested
|
||||
|
@ -381,16 +381,12 @@ typedef NS_ENUM(NSInteger, SectionIdentifier) {
|
||||
NSString* password = self.passwordText;
|
||||
NSString* note = self.noteText;
|
||||
|
||||
// TODO(crbug.com/330355124): Get the gaia ID if there's only 1 account OR
|
||||
// show some UI so that the user can pick which account to create the password
|
||||
// in.
|
||||
NSString* gaia = nil;
|
||||
|
||||
[self.credentialHandler saveCredentialWithUsername:username
|
||||
password:password
|
||||
note:note
|
||||
gaia:gaia
|
||||
shouldReplace:shouldReplace];
|
||||
[self.credentialHandler
|
||||
saveCredentialWithUsername:username
|
||||
password:password
|
||||
note:note
|
||||
gaia:[self.credentialHandler gaia]
|
||||
shouldReplace:shouldReplace];
|
||||
}
|
||||
|
||||
- (NSString*)noteFooterText {
|
||||
|
@ -17,6 +17,14 @@ NSString* PasswordWithKeychainIdentifier(NSString* identifier);
|
||||
// for later query. Returns `YES` if saving was successful and `NO` otherwise.
|
||||
BOOL StorePasswordInKeychain(NSString* password, NSString* identifier);
|
||||
|
||||
// Queries Keychain Services for the stored gaia.
|
||||
// Returns nil if no gaia is found.
|
||||
NSString* LoadGaiaFromKeychain();
|
||||
|
||||
// Stores `gaia` in Keychain Services.
|
||||
// Returns `YES` if saving was successful and `NO` otherwise.
|
||||
BOOL StoreGaiaInKeychain(NSString* gaia);
|
||||
|
||||
} // namespace credential_provider_extension
|
||||
|
||||
#endif // IOS_COMPONENTS_CREDENTIAL_PROVIDER_EXTENSION_PASSWORD_UTIL_H_
|
||||
|
@ -8,6 +8,41 @@
|
||||
|
||||
#import "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr NSString* kGaiaIdentifier = @"credential_provider_extension.gaia";
|
||||
|
||||
NSDictionary* GaiaLoadQuery() {
|
||||
return @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
|
||||
(__bridge id)kSecReturnData : @YES,
|
||||
};
|
||||
}
|
||||
|
||||
NSDictionary* GaiaStoreQuery(NSData* gaia) {
|
||||
return @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)
|
||||
kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
|
||||
(__bridge id)kSecValueData : gaia,
|
||||
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
NSDictionary* GaiaUpdateQuery() {
|
||||
return @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
NSDictionary* GaiaUpdateAttribs(NSData* gaia) {
|
||||
return @{(__bridge id)kSecValueData : gaia};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace credential_provider_extension {
|
||||
|
||||
NSString* PasswordWithKeychainIdentifier(NSString* identifier) {
|
||||
@ -55,4 +90,45 @@ BOOL StorePasswordInKeychain(NSString* password, NSString* identifier) {
|
||||
return status == errSecSuccess;
|
||||
}
|
||||
|
||||
NSString* LoadGaiaFromKeychain() {
|
||||
NSDictionary* gaiaLoadQuery = GaiaLoadQuery();
|
||||
|
||||
// Get the keychain item containing the password.
|
||||
CFDataRef sec_data_ref = nullptr;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)gaiaLoadQuery,
|
||||
(CFTypeRef*)&sec_data_ref);
|
||||
|
||||
if (status != errSecSuccess) {
|
||||
DLOG(ERROR) << "Error retrieving gaia, OSStatus: " << status;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// This is safe because SecItemCopyMatching either assign an owned reference
|
||||
// to sec_data_ref, or leave it unchanged, and bridging maps nullptr to nil.
|
||||
NSData* data = (__bridge_transfer NSData*)sec_data_ref;
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
BOOL StoreGaiaInKeychain(NSString* gaia) {
|
||||
NSData* gaiaData = [gaia dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
// Check that there is not already a gaia.
|
||||
OSStatus status =
|
||||
SecItemCopyMatching((__bridge CFDictionaryRef)GaiaLoadQuery(),
|
||||
/*result=*/nullptr);
|
||||
if (status == errSecItemNotFound) {
|
||||
// A new entry must be created.
|
||||
status = SecItemAdd((__bridge CFDictionaryRef)GaiaStoreQuery(gaiaData),
|
||||
/*result=*/nullptr);
|
||||
|
||||
} else if (status == noErr) {
|
||||
// The entry must be updated.
|
||||
status =
|
||||
SecItemUpdate((__bridge CFDictionaryRef)GaiaUpdateQuery(),
|
||||
(__bridge CFDictionaryRef)GaiaUpdateAttribs(gaiaData));
|
||||
}
|
||||
|
||||
return status == errSecSuccess;
|
||||
}
|
||||
|
||||
} // namespace credential_provider_extension
|
||||
|
@ -130,4 +130,18 @@ TEST_F(PasswordUtilKeychainTest, StoreEmptyIdentifier) {
|
||||
EXPECT_FALSE(StorePasswordInKeychain(kCredentialPassword1, @""));
|
||||
}
|
||||
|
||||
// Tests storing and loading a gaia.
|
||||
TEST_F(PasswordUtilKeychainTest, StoreGaia) {
|
||||
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey1));
|
||||
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey1);
|
||||
}
|
||||
|
||||
// Tests updating an existing gaia.
|
||||
TEST_F(PasswordUtilKeychainTest, UpdateGaia) {
|
||||
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey1));
|
||||
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey1);
|
||||
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey2));
|
||||
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey2);
|
||||
}
|
||||
|
||||
} // credential_provider_extension
|
||||
|
Reference in New Issue
Block a user