Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/*
2
 * Copyright 2019 Google
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h"
18
#import <Security/Security.h>
19
 
20
#if __has_include(<FBLPromises/FBLPromises.h>)
21
#import <FBLPromises/FBLPromises.h>
22
#else
23
#import "FBLPromises.h"
24
#endif
25
 
26
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h"
27
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
28
 
29
@interface GULKeychainStorage ()
30
@property(nonatomic, readonly) dispatch_queue_t keychainQueue;
31
@property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue;
32
@property(nonatomic, readonly) NSString *service;
33
@property(nonatomic, readonly) NSCache<NSString *, id<NSSecureCoding>> *inMemoryCache;
34
@end
35
 
36
@implementation GULKeychainStorage
37
 
38
- (instancetype)initWithService:(NSString *)service {
39
  NSCache *cache = [[NSCache alloc] init];
40
  // Cache up to 5 installations.
41
  cache.countLimit = 5;
42
  return [self initWithService:service cache:cache];
43
}
44
 
45
- (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache {
46
  self = [super init];
47
  if (self) {
48
    _keychainQueue =
49
        dispatch_queue_create("com.gul.KeychainStorage.Keychain", DISPATCH_QUEUE_SERIAL);
50
    _inMemoryCacheQueue =
51
        dispatch_queue_create("com.gul.KeychainStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL);
52
    _service = [service copy];
53
    _inMemoryCache = cache;
54
  }
55
  return self;
56
}
57
 
58
#pragma mark - Public
59
 
60
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
61
                                        objectClass:(Class)objectClass
62
                                        accessGroup:(nullable NSString *)accessGroup {
63
  return [FBLPromise onQueue:self.inMemoryCacheQueue
64
                          do:^id _Nullable {
65
                            // Return cached object or fail otherwise.
66
                            id object = [self.inMemoryCache objectForKey:key];
67
                            return object
68
                                       ?: [[NSError alloc]
69
                                              initWithDomain:FBLPromiseErrorDomain
70
                                                        code:FBLPromiseErrorCodeValidationFailure
71
                                                    userInfo:nil];
72
                          }]
73
      .recover(^id _Nullable(NSError *error) {
74
        // Look for the object in the keychain.
75
        return [self getObjectFromKeychainForKey:key
76
                                     objectClass:objectClass
77
                                     accessGroup:accessGroup];
78
      });
79
}
80
 
81
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
82
                             forKey:(NSString *)key
83
                        accessGroup:(nullable NSString *)accessGroup {
84
  return [FBLPromise onQueue:self.inMemoryCacheQueue
85
                          do:^id _Nullable {
86
                            // Save to the in-memory cache first.
87
                            [self.inMemoryCache setObject:object forKey:[key copy]];
88
                            return [NSNull null];
89
                          }]
90
      .thenOn(self.keychainQueue, ^id(id result) {
91
        // Then store the object to the keychain.
92
        NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
93
        NSError *error;
94
        NSData *encodedObject = [GULSecureCoding archivedDataWithRootObject:object error:&error];
95
        if (!encodedObject) {
96
          return error;
97
        }
98
 
99
        if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) {
100
          return error;
101
        }
102
 
103
        return [NSNull null];
104
      });
105
}
106
 
107
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
108
                                 accessGroup:(nullable NSString *)accessGroup {
109
  return [FBLPromise onQueue:self.inMemoryCacheQueue
110
                          do:^id _Nullable {
111
                            [self.inMemoryCache removeObjectForKey:key];
112
                            return nil;
113
                          }]
114
      .thenOn(self.keychainQueue, ^id(id result) {
115
        NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
116
 
117
        NSError *error;
118
        if (![GULKeychainUtils removeItemWithQuery:query error:&error]) {
119
          return error;
120
        }
121
 
122
        return [NSNull null];
123
      });
124
}
125
 
126
#pragma mark - Private
127
 
128
- (FBLPromise<id<NSSecureCoding>> *)getObjectFromKeychainForKey:(NSString *)key
129
                                                    objectClass:(Class)objectClass
130
                                                    accessGroup:(nullable NSString *)accessGroup {
131
  // Look for the object in the keychain.
132
  return [FBLPromise
133
             onQueue:self.keychainQueue
134
                  do:^id {
135
                    NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
136
                    NSError *error;
137
                    NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error];
138
 
139
                    if (error) {
140
                      return error;
141
                    }
142
                    if (!encodedObject) {
143
                      return nil;
144
                    }
145
                    id object = [GULSecureCoding unarchivedObjectOfClass:objectClass
146
                                                                fromData:encodedObject
147
                                                                   error:&error];
148
                    if (error) {
149
                      return error;
150
                    }
151
 
152
                    return object;
153
                  }]
154
      .thenOn(self.inMemoryCacheQueue,
155
              ^id<NSSecureCoding> _Nullable(id<NSSecureCoding> _Nullable object) {
156
                // Save object to the in-memory cache if exists and return the object.
157
                if (object) {
158
                  [self.inMemoryCache setObject:object forKey:[key copy]];
159
                }
160
                return object;
161
              });
162
}
163
 
164
- (void)resetInMemoryCache {
165
  [self.inMemoryCache removeAllObjects];
166
}
167
 
168
#pragma mark - Keychain
169
 
170
- (NSMutableDictionary<NSString *, id> *)keychainQueryWithKey:(NSString *)key
171
                                                  accessGroup:(nullable NSString *)accessGroup {
172
  NSMutableDictionary<NSString *, id> *query = [NSMutableDictionary dictionary];
173
 
174
  query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword;
175
  query[(__bridge NSString *)kSecAttrService] = self.service;
176
  query[(__bridge NSString *)kSecAttrAccount] = key;
177
 
178
  if (accessGroup) {
179
    query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
180
  }
181
 
182
#if TARGET_OS_OSX
183
  if (self.keychainRef) {
184
    query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef);
185
    query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ];
186
  }
187
#endif  // TARGET_OSX
188
 
189
  return query;
190
}
191
 
192
@end