blob: 5d9a84d00ce7a54ffca24a5e9daa327227e7feeb [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/common/credential_provider/archivable_credential_store.h"
#include "base/check.h"
#include "base/mac/foundation_util.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/common/credential_provider/archivable_credential.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ArchivableCredentialStore ()
// Working queue used to sync the mutable set operations.
@property(nonatomic) dispatch_queue_t workingQueue;
// The fileURL to the disk file, can be nil.
@property(nonatomic, strong) NSURL* fileURL;
// The in-memory storage.
@property(nonatomic, strong)
NSMutableDictionary<NSString*, ArchivableCredential*>* memoryStorage;
@end
@implementation ArchivableCredentialStore
#pragma mark - Public
- (instancetype)initWithFileURL:(NSURL*)fileURL {
self = [super init];
if (self) {
if (fileURL) {
DCHECK(fileURL.isFileURL) << "URL must be a file URL.";
}
_fileURL = fileURL;
_workingQueue = dispatch_queue_create(nullptr, DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
#pragma mark - CredentialStore
- (NSArray<id<Credential>>*)credentials {
__block NSArray<id<Credential>>* credentials;
dispatch_sync(self.workingQueue, ^{
credentials = [self.memoryStorage allValues];
});
return credentials;
}
- (void)saveDataWithCompletion:(void (^)(NSError* error))completion {
dispatch_barrier_async(self.workingQueue, ^{
auto executeCompletionIfPresent = ^(NSError* error) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}
};
if (!self.fileURL) {
// There is no fileURL, store is being used as memory only.
executeCompletionIfPresent(nil);
return;
}
NSError* error = nil;
NSData* data =
[NSKeyedArchiver archivedDataWithRootObject:self.memoryStorage
requiringSecureCoding:YES
error:&error];
DCHECK(!error) << base::SysNSStringToUTF8(error.description);
if (error) {
executeCompletionIfPresent(error);
return;
}
[[NSFileManager defaultManager]
createDirectoryAtURL:self.fileURL.URLByDeletingLastPathComponent
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
executeCompletionIfPresent(error);
return;
}
[data writeToURL:self.fileURL options:NSDataWritingAtomic error:&error];
DCHECK(!error) << base::SysNSStringToUTF8(error.description);
executeCompletionIfPresent(error);
});
}
- (void)removeAllCredentials {
dispatch_barrier_async(self.workingQueue, ^{
[self.memoryStorage removeAllObjects];
});
}
- (void)addCredential:(id<Credential>)credential {
DCHECK(credential.recordIdentifier)
<< "credential must have a record identifier";
dispatch_barrier_async(self.workingQueue, ^{
DCHECK(!self.memoryStorage[credential.recordIdentifier])
<< "Credential already exists in the storage";
self.memoryStorage[credential.recordIdentifier] =
base::mac::ObjCCastStrict<ArchivableCredential>(credential);
});
}
- (void)updateCredential:(id<Credential>)credential {
[self removeCredentialWithRecordIdentifier:credential.recordIdentifier];
[self addCredential:credential];
}
- (void)removeCredentialWithRecordIdentifier:(NSString*)recordIdentifier {
DCHECK(recordIdentifier.length) << "Invalid |recordIdentifier| was passed.";
dispatch_barrier_async(self.workingQueue, ^{
DCHECK(self.memoryStorage[recordIdentifier])
<< "Credential doesn't exist in the storage";
self.memoryStorage[recordIdentifier] = nil;
});
}
- (id<Credential>)credentialWithRecordIdentifier:(NSString*)recordIdentifier {
DCHECK(recordIdentifier.length);
__block id<Credential> credential;
dispatch_sync(self.workingQueue, ^{
credential = self.memoryStorage[recordIdentifier];
});
return credential;
}
#pragma mark - Getters
- (NSMutableDictionary<NSString*, ArchivableCredential*>*)memoryStorage {
#if !defined(NDEBUG)
dispatch_assert_queue(self.workingQueue);
#endif // !defined(NDEBUG)
if (!_memoryStorage) {
_memoryStorage = [self loadStorage];
}
return _memoryStorage;
}
#pragma mark - Private
// Loads the store from disk.
- (NSMutableDictionary<NSString*, ArchivableCredential*>*)loadStorage {
#if !defined(NDEBUG)
dispatch_assert_queue(self.workingQueue);
#endif // !defined(NDEBUG)
if (!self.fileURL) {
return [[NSMutableDictionary alloc] init];
}
NSError* error = nil;
[self.fileURL checkResourceIsReachableAndReturnError:&error];
if (error) {
if (error.code == NSFileReadNoSuchFileError) {
// File has not been created, return a fresh mutable set.
return [[NSMutableDictionary alloc] init];
}
NOTREACHED();
}
NSData* data = [NSData dataWithContentsOfURL:self.fileURL
options:0
error:&error];
DCHECK(!error) << base::SysNSStringToUTF8(error.description);
NSSet* classes = [NSSet setWithObjects:[ArchivableCredential class],
[NSMutableDictionary class], nil];
NSMutableDictionary<NSString*, ArchivableCredential*>* dictionary =
[NSKeyedUnarchiver unarchivedObjectOfClasses:classes
fromData:data
error:&error];
DCHECK(!error) << base::SysNSStringToUTF8(error.description);
return dictionary;
}
@end