1// Copyright © 2019-present 650 Industries. All rights reserved.
2
3#if __has_include(<EXSecureStore/EXSecureStore.h>)
4#import "EXScopedSecureStore.h"
5
6@interface EXSecureStore (Protected)
7
8- (NSString *)validatedKey:(NSString *)key;
9- (NSData *)_searchKeychainWithKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error;
10- (BOOL)_setValue:(NSString *)value withKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error;
11- (void)_deleteValueWithKey:(NSString *)key withOptions:(NSDictionary *)options;
12+ (NSString *) _messageForError:(NSError *)error;
13
14@end
15
16@interface EXScopedSecureStore ()
17
18@property (strong, nonatomic) NSString *scopeKey;
19@property (nonatomic) BOOL isStandaloneApp;
20
21@end
22
23@implementation EXScopedSecureStore
24
25- (instancetype)initWithScopeKey:(NSString *)scopeKey
26                       andConstantsBinding:(EXConstantsBinding *)constantsBinding
27{
28  if (self = [super init]) {
29    _scopeKey = scopeKey;
30    _isStandaloneApp = ![@"expo" isEqualToString:constantsBinding.appOwnership];
31  }
32  return self;
33}
34
35- (NSString *)validatedKey:(NSString *)key {
36  if (![super validatedKey:key]) {
37    return nil;
38  }
39
40  return _isStandaloneApp ? key : [NSString stringWithFormat:@"%@-%@", _scopeKey, key];
41}
42
43// We must override this method so that items saved in standalone apps on SDK 40 and below,
44// which were scoped by prefixing the validated key with the scopeKey, can still be
45// found in SDK 41 and up. This override can be removed in SDK 45.
46- (NSString *)_getValueWithKey:(NSString *)key
47                   withOptions:(NSDictionary *)options
48                         error:(NSError **)error __deprecated_msg("To be removed once SDK 41 is phased out")
49{
50  NSError *searchError;
51  NSData *data = [self _searchKeychainWithKey:key
52                                  withOptions:options
53                                        error:&searchError];
54  if (data) {
55    NSString *value = [[NSString alloc] initWithData:data
56                                            encoding:NSUTF8StringEncoding];
57    return value;
58  } else if (_isStandaloneApp) {
59    NSString *scopedKey = [NSString stringWithFormat:@"%@-%@", _scopeKey, key];
60    NSString *scopedValue = [self getValueWithScopedKey:scopedKey
61                                             withOptions:options];
62    if (scopedValue) {
63      [self migrateValue:scopedValue
64            fromScopedKey:scopedKey
65                 toNewKey:key
66              withOptions:options];
67      return scopedValue;
68    }
69    // If we don't find anything under the scopedKey, we want to return
70    // the original error from searching for the unscoped key.
71  }
72
73  *error = searchError;
74  return nil;
75}
76
77- (NSString *)getValueWithScopedKey:(NSString *)scopedKey withOptions:(NSDictionary *)options
78{
79  NSError *searchError;
80  NSData *data = [self _searchKeychainWithKey:scopedKey
81                                  withOptions:options
82                                        error:&searchError];
83  if (data) {
84    NSString *value = [[NSString alloc] initWithData:data
85                                            encoding:NSUTF8StringEncoding];
86    return value;
87  }
88  return nil;
89}
90
91- (void)migrateValue:(NSString *)value
92       fromScopedKey:(NSString *)scopedKey
93            toNewKey:(NSString *)newKey
94         withOptions:(NSDictionary *)options
95{
96  // Migrate the value to unscoped storage, then delete the legacy
97  // value if successful.
98  NSError *error;
99  BOOL setValue = [self _setValue:value
100                          withKey:newKey
101                      withOptions:options
102                            error:&error];
103  if (setValue) {
104    [self _deleteValueWithKey:scopedKey
105                  withOptions:options];
106  } else {
107    EXLogWarn(@"Encountered an error while saving SecureStore data: %@.", [[super class] _messageForError:error]);
108  }
109}
110
111@end
112#endif
113