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 withOptions:(NSDictionary *)options error:(NSError **)error
47{
48  NSError *searchError;
49  NSData *data = [self _searchKeychainWithKey:key
50                                  withOptions:options
51                                        error:&searchError];
52  if (data) {
53    NSString *value = [[NSString alloc] initWithData:data
54                                            encoding:NSUTF8StringEncoding];
55    return value;
56  } else if (_isStandaloneApp) {
57    NSString *scopedKey = [NSString stringWithFormat:@"%@-%@", _scopeKey, key];
58    NSString *scopedValue = [self getValueWithScopedKey:scopedKey
59                                             withOptions:options];
60    if (scopedValue) {
61      [self migrateValue:scopedValue
62            fromScopedKey:scopedKey
63                 toNewKey:key
64              withOptions:options];
65      return scopedValue;
66    }
67    // If we don't find anything under the scopedKey, we want to return
68    // the original error from searching for the unscoped key.
69  }
70
71  *error = searchError;
72  return nil;
73}
74
75- (NSString *)getValueWithScopedKey:(NSString *)scopedKey withOptions:(NSDictionary *)options
76{
77  NSError *searchError;
78  NSData *data = [self _searchKeychainWithKey:scopedKey
79                                  withOptions:options
80                                        error:&searchError];
81  if (data) {
82    NSString *value = [[NSString alloc] initWithData:data
83                                            encoding:NSUTF8StringEncoding];
84    return value;
85  }
86  return nil;
87}
88
89- (void)migrateValue:(NSString *)value
90       fromScopedKey:(NSString *)scopedKey
91            toNewKey:(NSString *)newKey
92         withOptions:(NSDictionary *)options
93{
94  // Migrate the value to unscoped storage, then delete the legacy
95  // value if successful.
96  NSError *error;
97  BOOL setValue = [self _setValue:value
98                          withKey:newKey
99                      withOptions:options
100                            error:&error];
101  if (setValue) {
102    [self _deleteValueWithKey:scopedKey
103                  withOptions:options];
104  } else {
105    EXLogWarn(@"Encountered an error while saving SecureStore data: %@.", [[super class] _messageForError:error]);
106  }
107}
108
109@end
110#endif
111