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