Add test app context; use mock "document" and "shared data container" directories in tests, use mock keychain storage in tests.
This commit is contained in:
parent
0357699fc8
commit
399dd13cee
|
@ -11,10 +11,10 @@
|
|||
#import "TSAccountManager.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import "ViewControllerUtils.h"
|
||||
#import <SAMKeychain/SAMKeychain.h>
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/NSString+OWS.h>
|
||||
#import <SignalMessaging/OWSNavigationController.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -566,11 +566,12 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi
|
|||
OWSCAssertDebug(key.length > 0);
|
||||
|
||||
NSError *error;
|
||||
NSString *value = [SAMKeychain passwordForService:kKeychainService_LastRegistered account:key error:&error];
|
||||
if (value && !error) {
|
||||
return value;
|
||||
NSString *_Nullable value =
|
||||
[CurrentAppContext().keychainStorage stringForKey:key service:kKeychainService_LastRegistered error:&error];
|
||||
if (error || !value) {
|
||||
DDLogWarn(@"Could not retrieve 'last registered' value from keychain: %@.", error);
|
||||
}
|
||||
return nil;
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setDebugValue:(NSString *)value forKey:(NSString *)key
|
||||
|
@ -580,8 +581,10 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi
|
|||
OWSCAssertDebug(value.length > 0);
|
||||
|
||||
NSError *error;
|
||||
[SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
|
||||
BOOL success = [SAMKeychain setPassword:value forService:kKeychainService_LastRegistered account:key error:&error];
|
||||
BOOL success = [CurrentAppContext().keychainStorage setWithString:value
|
||||
forKey:key
|
||||
service:kKeychainService_LastRegistered
|
||||
error:&error];
|
||||
if (!success || error) {
|
||||
OWSLogError(@"Error persisting 'last registered' value in keychain: %@", error);
|
||||
}
|
||||
|
|
|
@ -283,6 +283,26 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
backgroundTask = nil;
|
||||
}
|
||||
|
||||
- (id<KeychainStorage>)keychainStorage
|
||||
{
|
||||
return [SSKKeychainStorage sharedInstance];
|
||||
}
|
||||
|
||||
- (NSString *)appDocumentDirectoryPath
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *documentDirectoryURL =
|
||||
[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
return [documentDirectoryURL path];
|
||||
}
|
||||
|
||||
- (NSString *)appSharedDataDirectoryPath
|
||||
{
|
||||
NSURL *groupContainerDirectoryURL =
|
||||
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup];
|
||||
return [groupContainerDirectoryURL path];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#import "OWSBackupIO.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <Curve25519Kit/Randomness.h>
|
||||
#import <SAMKeychain/SAMKeychain.h>
|
||||
#import <YapDatabase/YapDatabaseCryptoUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SAMKeychain
|
||||
|
||||
public enum KeychainStorageError: Error {
|
||||
case failure(description: String)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc public protocol KeychainStorage: class {
|
||||
|
||||
@objc func string(forKey key: String, service: String) throws -> String
|
||||
|
||||
@objc func set(string: String, forKey key: String, service: String) throws
|
||||
|
||||
@objc func data(forKey key: String, service: String) throws -> Data
|
||||
|
||||
@objc func set(data: Data, forKey key: String, service: String) throws
|
||||
|
||||
@objc func remove(key: String, service: String) throws
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class SSKKeychainStorage: NSObject, KeychainStorage {
|
||||
|
||||
@objc public static let sharedInstance = SSKKeychainStorage()
|
||||
|
||||
// Force usage as a singleton
|
||||
override private init() {
|
||||
super.init()
|
||||
|
||||
SwiftSingletons.register(self)
|
||||
}
|
||||
|
||||
@objc public func string(forKey key: String, service: String) throws -> String {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.password(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) error retrieving string: \(error)")
|
||||
}
|
||||
guard let string = result else {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) could not retrieve string")
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
@objc public func set(string: String, forKey key: String, service: String) throws {
|
||||
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.setPassword(string, forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) error setting string: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) could not set string")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func data(forKey key: String, service: String) throws -> Data {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.passwordData(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) error retrieving data: \(error)")
|
||||
}
|
||||
guard let data = result else {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) could not retrieve data")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@objc public func set(data: Data, forKey key: String, service: String) throws {
|
||||
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.setPasswordData(data, forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) error setting data: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) could not set data")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func remove(key: String, service: String) throws {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.deletePassword(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) error removing data: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw SSKProtoError.invalidProtobuf(description: "\(logTag) could not remove data")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppContext.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TestAppContext : NSObject <AppContext>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TestAppContext.h"
|
||||
#import "TestKeychainStorage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TestAppContext ()
|
||||
|
||||
@property (nonatomic) TestKeychainStorage *testKeychainStorage;
|
||||
@property (nonatomic) NSString *appDocumentDirPath;
|
||||
@property (nonatomic) NSString *appSharedDataDirPath;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation TestAppContext
|
||||
|
||||
@synthesize mainWindow = _mainWindow;
|
||||
@synthesize appLaunchTime = _appLaunchTime;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self.testKeychainStorage = [TestKeychainStorage new];
|
||||
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
self.appDocumentDirPath = [temporaryDirectory stringByAppendingPathComponent:NSUUID.UUID.UUIDString];
|
||||
self.appSharedDataDirPath = [temporaryDirectory stringByAppendingPathComponent:NSUUID.UUID.UUIDString];
|
||||
|
||||
_appLaunchTime = [NSDate new];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIApplicationState)reportedApplicationState
|
||||
{
|
||||
return UIApplicationStateActive;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)isMainApp
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isMainAppAndActive
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isRTL
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated
|
||||
{
|
||||
}
|
||||
|
||||
- (CGFloat)statusBarHeight
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
- (BOOL)isInBackground
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isAppForegroundAndActive
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:
|
||||
(BackgroundTaskExpirationHandler)expirationHandler
|
||||
{
|
||||
return UIBackgroundTaskInvalid;
|
||||
}
|
||||
|
||||
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier
|
||||
{
|
||||
}
|
||||
|
||||
- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray<id> *)blockingObjects
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setMainAppBadgeNumber:(NSInteger)value
|
||||
{
|
||||
}
|
||||
|
||||
- (nullable UIViewController *)frontmostViewController
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (nullable UIAlertAction *)openSystemSettingsAction
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)doMultiDeviceUpdateWithProfileKey:(OWSAES256Key *)profileKey
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL)isRunningTests
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setNetworkActivityIndicatorVisible:(BOOL)value
|
||||
{
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block
|
||||
{
|
||||
block();
|
||||
}
|
||||
|
||||
- (void)runAppActiveBlocks
|
||||
{
|
||||
}
|
||||
|
||||
- (id<KeychainStorage>)keychainStorage
|
||||
{
|
||||
return self.testKeychainStorage;
|
||||
}
|
||||
|
||||
- (NSString *)appDocumentDirectoryPath
|
||||
{
|
||||
return self.appDocumentDirPath;
|
||||
}
|
||||
|
||||
- (NSString *)appSharedDataDirectoryPath
|
||||
{
|
||||
return self.appSharedDataDirPath;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol KeychainStorage;
|
||||
|
||||
@interface TestKeychainStorage : NSObject <KeychainStorage>
|
||||
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, NSData *> *dataMap;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TestKeychainStorage.h"
|
||||
#import "NSData+OWS.h"
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation TestKeychainStorage
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self.dataMap = [NSMutableDictionary new];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)stringForKey:(NSString *)key
|
||||
service:(NSString *)service
|
||||
error:(NSError *_Nullable *_Nullable)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(service.length > 0);
|
||||
|
||||
*error = nil;
|
||||
|
||||
NSString *mapKey = [NSString stringWithFormat:@"%@-%@", service, key];
|
||||
NSData *_Nullable data = self.dataMap[mapKey];
|
||||
if (!data) {
|
||||
NSLog(@"stringForKey:%@ service:%@ -> nil", key, service);
|
||||
return nil;
|
||||
}
|
||||
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
NSLog(@"stringForKey:%@ service:%@ -> %@", key, service, string);
|
||||
return string;
|
||||
}
|
||||
|
||||
- (BOOL)setWithString:(NSString *)string
|
||||
forKey:(NSString *)key
|
||||
service:(NSString *)service
|
||||
error:(NSError *_Nullable *_Nullable)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(service.length > 0);
|
||||
|
||||
*error = nil;
|
||||
|
||||
NSLog(@"setWithString:%@ service:%@ -> %@", key, service, string);
|
||||
|
||||
NSString *mapKey = [NSString stringWithFormat:@"%@-%@", service, key];
|
||||
self.dataMap[mapKey] = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSData *_Nullable)dataForKey:(NSString *)key service:(NSString *)service error:(NSError *_Nullable *_Nullable)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(service.length > 0);
|
||||
|
||||
*error = nil;
|
||||
|
||||
NSString *mapKey = [NSString stringWithFormat:@"%@-%@", service, key];
|
||||
NSData *_Nullable data = self.dataMap[mapKey];
|
||||
NSLog(@"dataForKey:%@ service:%@ -> %@", key, service, data.hexadecimalString);
|
||||
return data;
|
||||
}
|
||||
|
||||
- (BOOL)setWithData:(NSData *)data
|
||||
forKey:(NSString *)key
|
||||
service:(NSString *)service
|
||||
error:(NSError *_Nullable *_Nullable)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(service.length > 0);
|
||||
|
||||
*error = nil;
|
||||
|
||||
NSLog(@"setWithData:%@ service:%@ -> %@", key, service, data.hexadecimalString);
|
||||
|
||||
NSString *mapKey = [NSString stringWithFormat:@"%@-%@", service, key];
|
||||
self.dataMap[mapKey] = data;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)removeWithKey:(NSString *)key service:(NSString *)service error:(NSError *_Nullable *_Nullable)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
OWSAssert(key.length > 0);
|
||||
OWSAssert(service.length > 0);
|
||||
|
||||
*error = nil;
|
||||
|
||||
NSLog(@"removeWithKey:%@ service:%@", key, service);
|
||||
|
||||
NSString *mapKey = [NSString stringWithFormat:@"%@-%@", service, key];
|
||||
[self.dataMap removeObjectForKey:mapKey];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -22,6 +22,8 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
|
|||
|
||||
@class OWSAES256Key;
|
||||
|
||||
@protocol KeychainStorage;
|
||||
|
||||
@protocol AppContext <NSObject>
|
||||
|
||||
@property (nonatomic, readonly) BOOL isMainApp;
|
||||
|
@ -96,6 +98,12 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
|
|||
|
||||
@property (atomic, readonly) NSDate *appLaunchTime;
|
||||
|
||||
- (id<KeychainStorage>)keychainStorage;
|
||||
|
||||
- (NSString *)appDocumentDirectoryPath;
|
||||
|
||||
- (NSString *)appSharedDataDirectoryPath;
|
||||
|
||||
@end
|
||||
|
||||
id<AppContext> CurrentAppContext(void);
|
||||
|
@ -103,4 +111,8 @@ void SetCurrentAppContext(id<AppContext> appContext);
|
|||
|
||||
void ExitShareExtension(void);
|
||||
|
||||
#ifdef DEBUG
|
||||
void ClearCurrentAppContextForTests(void);
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -43,6 +43,11 @@ void SetCurrentAppContext(id<AppContext> appContext)
|
|||
currentAppContext = appContext;
|
||||
}
|
||||
|
||||
void ClearCurrentAppContextForTests()
|
||||
{
|
||||
currentAppContext = nil;
|
||||
}
|
||||
|
||||
void ExitShareExtension(void)
|
||||
{
|
||||
OWSLogInfo(@"ExitShareExtension");
|
||||
|
|
|
@ -102,17 +102,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
+ (NSString *)appDocumentDirectoryPath
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *documentDirectoryURL =
|
||||
[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
return [documentDirectoryURL path];
|
||||
return CurrentAppContext().appDocumentDirectoryPath;
|
||||
}
|
||||
|
||||
+ (NSString *)appSharedDataDirectoryPath
|
||||
{
|
||||
NSURL *groupContainerDirectoryURL =
|
||||
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup];
|
||||
return [groupContainerDirectoryURL path];
|
||||
return CurrentAppContext().appSharedDataDirectoryPath;
|
||||
}
|
||||
|
||||
+ (NSString *)cachesDirectoryPath
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#import "ShareAppExtensionContext.h"
|
||||
#import <SignalMessaging/UIViewController+OWS.h>
|
||||
#import <SignalServiceKit/OWSStorage.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <SignalServiceKit/TSConstants.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -212,6 +214,26 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSFailDebug(@"cannot run main app active blocks in share extension.");
|
||||
}
|
||||
|
||||
- (id<KeychainStorage>)keychainStorage
|
||||
{
|
||||
return [SSKKeychainStorage sharedInstance];
|
||||
}
|
||||
|
||||
- (NSString *)appDocumentDirectoryPath
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *documentDirectoryURL =
|
||||
[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
return [documentDirectoryURL path];
|
||||
}
|
||||
|
||||
- (NSString *)appSharedDataDirectoryPath
|
||||
{
|
||||
NSURL *groupContainerDirectoryURL =
|
||||
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup];
|
||||
return [groupContainerDirectoryURL path];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
Loading…
Reference in New Issue