18a20a963SEric Samelson// Copyright 2015-present 650 Industries. All rights reserved. 28a20a963SEric Samelson 38a20a963SEric Samelson#import "EXSession.h" 48a20a963SEric Samelson#import "EXUnversioned.h" 58a20a963SEric Samelson 68a20a963SEric SamelsonNSString * const kEXSessionKeychainKey = @"host.exp.exponent.session"; 78a20a963SEric SamelsonNSString * const kEXSessionKeychainService = @"app"; 88a20a963SEric Samelson 98a20a963SEric Samelson@interface EXSession () 108a20a963SEric Samelson 118a20a963SEric Samelson@property (nonatomic, strong) NSDictionary *session; 128a20a963SEric Samelson 138a20a963SEric Samelson@end 148a20a963SEric Samelson 158a20a963SEric Samelson@implementation EXSession 168a20a963SEric Samelson 178a20a963SEric Samelson+ (nonnull instancetype)sharedInstance 188a20a963SEric Samelson{ 198a20a963SEric Samelson static EXSession *theSession; 208a20a963SEric Samelson static dispatch_once_t once; 218a20a963SEric Samelson dispatch_once(&once, ^{ 228a20a963SEric Samelson if (!theSession) { 238a20a963SEric Samelson theSession = [[EXSession alloc] init]; 248a20a963SEric Samelson } 258a20a963SEric Samelson }); 268a20a963SEric Samelson return theSession; 278a20a963SEric Samelson} 288a20a963SEric Samelson 298a20a963SEric Samelson- (NSDictionary * _Nullable)session 308a20a963SEric Samelson{ 318a20a963SEric Samelson if (_session) { 328a20a963SEric Samelson return _session; 338a20a963SEric Samelson } 348a20a963SEric Samelson NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{ 358a20a963SEric Samelson (__bridge id)kSecMatchLimit:(__bridge id)kSecMatchLimitOne, 368a20a963SEric Samelson (__bridge id)kSecReturnData:(__bridge id)kCFBooleanTrue 378a20a963SEric Samelson }]; 388a20a963SEric Samelson [query addEntriesFromDictionary:[self _searchQuery]]; 398a20a963SEric Samelson 408a20a963SEric Samelson CFTypeRef foundDict = NULL; 418a20a963SEric Samelson OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &foundDict); 428a20a963SEric Samelson 438a20a963SEric Samelson if (status == noErr) { 448a20a963SEric Samelson NSData *result = (__bridge_transfer NSData *)foundDict; 458a20a963SEric Samelson NSError *jsonError; 468a20a963SEric Samelson id session = [NSJSONSerialization JSONObjectWithData:result 478a20a963SEric Samelson options:kNilOptions 488a20a963SEric Samelson error:&jsonError]; 498a20a963SEric Samelson if (!jsonError && [session isKindOfClass:[NSDictionary class]]) { 508a20a963SEric Samelson return (NSDictionary *)session; 518a20a963SEric Samelson } 528a20a963SEric Samelson } 538a20a963SEric Samelson return nil; 548a20a963SEric Samelson} 558a20a963SEric Samelson 568a20a963SEric Samelson- (NSString * _Nullable)sessionSecret 578a20a963SEric Samelson{ 588a20a963SEric Samelson NSDictionary *session = [self session]; 598a20a963SEric Samelson if (!session) { 608a20a963SEric Samelson return nil; 618a20a963SEric Samelson } 628a20a963SEric Samelson 638a20a963SEric Samelson id sessionSecret = session[@"sessionSecret"]; 648a20a963SEric Samelson if (sessionSecret && [sessionSecret isKindOfClass:[NSString class]]) { 658a20a963SEric Samelson return (NSString *)sessionSecret; 668a20a963SEric Samelson } 678a20a963SEric Samelson return nil; 688a20a963SEric Samelson} 698a20a963SEric Samelson 708a20a963SEric Samelson- (BOOL)saveSessionToKeychain:(NSDictionary *)session error:(NSError **)error 718a20a963SEric Samelson{ 728a20a963SEric Samelson NSError *jsonError; 738a20a963SEric Samelson NSData *encodedData = [NSJSONSerialization dataWithJSONObject:session 748a20a963SEric Samelson options:kNilOptions 758a20a963SEric Samelson error:&jsonError]; 768a20a963SEric Samelson if (jsonError) { 778a20a963SEric Samelson if (error) { 788a20a963SEric Samelson *error = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") 798a20a963SEric Samelson code:-1 808a20a963SEric Samelson userInfo:@{ 818a20a963SEric Samelson NSLocalizedDescriptionKey: @"Could not serialize JSON to save session to keychain", 828a20a963SEric Samelson NSUnderlyingErrorKey: jsonError 838a20a963SEric Samelson }]; 848a20a963SEric Samelson } 858a20a963SEric Samelson return NO; 868a20a963SEric Samelson } 878a20a963SEric Samelson 888a20a963SEric Samelson NSDictionary *searchQuery = [self _searchQuery]; 898a20a963SEric Samelson NSDictionary *updateQuery = @{ (__bridge id)kSecValueData:encodedData }; 908a20a963SEric Samelson NSMutableDictionary *addQuery = [NSMutableDictionary dictionaryWithDictionary:searchQuery]; 918a20a963SEric Samelson [addQuery addEntriesFromDictionary:updateQuery]; 928a20a963SEric Samelson 938a20a963SEric Samelson OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL); 948a20a963SEric Samelson 958a20a963SEric Samelson if (status == errSecDuplicateItem) { 968a20a963SEric Samelson status = SecItemUpdate((__bridge CFDictionaryRef)searchQuery, (__bridge CFDictionaryRef)updateQuery); 978a20a963SEric Samelson } 988a20a963SEric Samelson 998a20a963SEric Samelson if (status == errSecSuccess) { 1008a20a963SEric Samelson _session = session; 1018a20a963SEric Samelson return YES; 1028a20a963SEric Samelson } else { 1038a20a963SEric Samelson if (error) { 1048a20a963SEric Samelson *error = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") 1058a20a963SEric Samelson code:-1 1068a20a963SEric Samelson userInfo:@{ NSLocalizedDescriptionKey: @"Could not save session to keychain" }]; 1078a20a963SEric Samelson } 1088a20a963SEric Samelson return NO; 1098a20a963SEric Samelson } 1108a20a963SEric Samelson} 1118a20a963SEric Samelson 1128a20a963SEric Samelson- (BOOL)deleteSessionFromKeychainWithError:(NSError **)error 1138a20a963SEric Samelson{ 1148a20a963SEric Samelson OSStatus status = SecItemDelete((__bridge CFDictionaryRef)[self _searchQuery]); 1158a20a963SEric Samelson 116*e1bdffa3SJames Ide if (status == errSecSuccess || status == errSecItemNotFound) { 1178a20a963SEric Samelson _session = nil; 1188a20a963SEric Samelson return YES; 1198a20a963SEric Samelson } else { 1208a20a963SEric Samelson if (error) { 1218a20a963SEric Samelson *error = [NSError errorWithDomain:EX_UNVERSIONED(@"EXKernelErrorDomain") 1228a20a963SEric Samelson code:-1 1238a20a963SEric Samelson userInfo:@{ NSLocalizedDescriptionKey: @"Could not delete session from keychain" }]; 1248a20a963SEric Samelson } 1258a20a963SEric Samelson return NO; 1268a20a963SEric Samelson } 1278a20a963SEric Samelson} 1288a20a963SEric Samelson 1298a20a963SEric Samelson- (NSDictionary *)_searchQuery 1308a20a963SEric Samelson{ 1318a20a963SEric Samelson NSData *encodedKey = [kEXSessionKeychainKey dataUsingEncoding:NSUTF8StringEncoding]; 1328a20a963SEric Samelson return @{ 1338a20a963SEric Samelson (__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword, 1348a20a963SEric Samelson (__bridge id)kSecAttrService:kEXSessionKeychainService, 1358a20a963SEric Samelson (__bridge id)kSecAttrGeneric:encodedKey, 1368a20a963SEric Samelson (__bridge id)kSecAttrAccount:encodedKey 1378a20a963SEric Samelson }; 1388a20a963SEric Samelson} 1398a20a963SEric Samelson 1408a20a963SEric Samelson@end 141