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 "FirebaseMessaging/Sources/Token/FIRMessagingAuthKeychain.h"
18
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
19
#import "FirebaseMessaging/Sources/Token/FIRMessagingKeychain.h"
20
 
21
/**
22
 *  The error type representing why we couldn't read data from the keychain.
23
 */
24
typedef NS_ENUM(int, FIRMessagingKeychainErrorType) {
25
  kFIRMessagingKeychainErrorBadArguments = -1301,
26
};
27
 
28
NSString *const kFIRMessagingKeychainWildcardIdentifier = @"*";
29
 
30
@interface FIRMessagingAuthKeychain ()
31
 
32
@property(nonatomic, copy) NSString *generic;
33
// cachedKeychainData is keyed by service and account, the value is an array of NSData.
34
// It is used to cache the tokens per service, per account, as well as checkin data per service,
35
// per account inside the keychain.
36
@property(nonatomic, strong)
37
    NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSArray<NSData *> *> *>
38
        *cachedKeychainData;
39
 
40
@end
41
 
42
@implementation FIRMessagingAuthKeychain
43
 
44
- (instancetype)initWithIdentifier:(NSString *)identifier {
45
  self = [super init];
46
  if (self) {
47
    _generic = [identifier copy];
48
    _cachedKeychainData = [[NSMutableDictionary alloc] init];
49
  }
50
  return self;
51
}
52
 
53
+ (NSMutableDictionary *)keychainQueryForService:(NSString *)service
54
                                         account:(NSString *)account
55
                                         generic:(NSString *)generic {
56
  NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword};
57
 
58
  NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query];
59
  if ([generic length] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:generic]) {
60
    finalQuery[(__bridge NSString *)kSecAttrGeneric] = generic;
61
  }
62
  if ([account length] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:account]) {
63
    finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
64
  }
65
  if ([service length] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:service]) {
66
    finalQuery[(__bridge NSString *)kSecAttrService] = service;
67
  }
68
  return finalQuery;
69
}
70
 
71
- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
72
  return [[self class] keychainQueryForService:service account:account generic:self.generic];
73
}
74
 
75
- (NSArray<NSData *> *)itemsMatchingService:(NSString *)service account:(NSString *)account {
76
  // If query wildcard service, it asks for all the results, which always query from keychain.
77
  if (![service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] &&
78
      ![account isEqualToString:kFIRMessagingKeychainWildcardIdentifier] &&
79
      _cachedKeychainData[service][account]) {
80
    // As long as service, account array exist, even it's empty, it means we've queried it before,
81
    // returns the cache value.
82
    return _cachedKeychainData[service][account];
83
  }
84
 
85
  NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
86
  NSMutableArray<NSData *> *results;
87
  keychainQuery[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
88
#if TARGET_OS_IOS || TARGET_OS_TV
89
  keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
90
  keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
91
  // FIRMessagingKeychain should only take a query and return a result, will handle the query here.
92
  NSArray *passwordInfos =
93
      CFBridgingRelease([[FIRMessagingKeychain sharedInstance] itemWithQuery:keychainQuery]);
94
#elif TARGET_OS_OSX || TARGET_OS_WATCH
95
  keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
96
  NSData *passwordInfos =
97
      CFBridgingRelease([[FIRMessagingKeychain sharedInstance] itemWithQuery:keychainQuery]);
98
#endif
99
 
100
  if (!passwordInfos) {
101
    // Nothing was found, simply return from this sync block.
102
    // Make sure to label the cache entry empty, signaling that we've queried this entry.
103
    if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
104
        [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
105
      // Do not update cache if it's wildcard query.
106
      return @[];
107
    } else if (_cachedKeychainData[service]) {
108
      [_cachedKeychainData[service] setObject:@[] forKey:account];
109
    } else {
110
      [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
111
    }
112
    return @[];
113
  }
114
  results = [[NSMutableArray alloc] init];
115
#if TARGET_OS_IOS || TARGET_OS_TV
116
  NSInteger numPasswords = passwordInfos.count;
117
  for (NSUInteger i = 0; i < numPasswords; i++) {
118
    NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i];
119
    if (passwordInfo[(__bridge id)kSecValueData]) {
120
      [results addObject:passwordInfo[(__bridge id)kSecValueData]];
121
    }
122
  }
123
#elif TARGET_OS_OSX || TARGET_OS_WATCH
124
  [results addObject:passwordInfos];
125
#endif
126
  // We query the keychain because it didn't exist in cache, now query is done, update the result in
127
  // the cache.
128
  if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
129
      [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
130
    // Do not update cache if it's wildcard query.
131
    return [results copy];
132
  } else if (_cachedKeychainData[service]) {
133
    [_cachedKeychainData[service] setObject:[results copy] forKey:account];
134
  } else {
135
    NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy];
136
    [_cachedKeychainData setObject:entry forKey:service];
137
  }
138
  return [results copy];
139
}
140
 
141
- (NSData *)dataForService:(NSString *)service account:(NSString *)account {
142
  NSArray<NSData *> *items = [self itemsMatchingService:service account:account];
143
  // If items is nil or empty, nil will be returned.
144
  return items.firstObject;
145
}
146
 
147
- (void)removeItemsMatchingService:(NSString *)service
148
                           account:(NSString *)account
149
                           handler:(void (^)(NSError *error))handler {
150
  if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
151
    // Delete all keychain items.
152
    _cachedKeychainData = [[NSMutableDictionary alloc] init];
153
  } else if ([account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
154
    // Delete all entries under service,
155
    if (_cachedKeychainData[service]) {
156
      _cachedKeychainData[service] = [[NSMutableDictionary alloc] init];
157
    }
158
  } else if (_cachedKeychainData[service]) {
159
    // We should keep the service/account entry instead of nil so we know
160
    // it's "empty entry" instead of "not query from keychain yet".
161
    [_cachedKeychainData[service] setObject:@[] forKey:account];
162
  } else {
163
    [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
164
  }
165
  NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
166
  [[FIRMessagingKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler];
167
}
168
 
169
- (void)setData:(NSData *)data
170
     forService:(NSString *)service
171
        account:(NSString *)account
172
        handler:(void (^)(NSError *))handler {
173
  if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
174
      [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
175
    if (handler) {
176
      handler([NSError errorWithDomain:kFIRMessagingKeychainErrorDomain
177
                                  code:kFIRMessagingKeychainErrorBadArguments
178
                              userInfo:nil]);
179
    }
180
    return;
181
  }
182
  [self removeItemsMatchingService:service
183
                           account:account
184
                           handler:^(NSError *error) {
185
                             if (error) {
186
                               if (handler) {
187
                                 handler(error);
188
                               }
189
                               return;
190
                             }
191
                             if (data.length > 0) {
192
                               NSMutableDictionary *keychainQuery =
193
                                   [self keychainQueryForService:service account:account];
194
                               keychainQuery[(__bridge id)kSecValueData] = data;
195
 
196
                               keychainQuery[(__bridge id)kSecAttrAccessible] =
197
                                   (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
198
                               [[FIRMessagingKeychain sharedInstance] addItemWithQuery:keychainQuery
199
                                                                               handler:handler];
200
                             }
201
                           }];
202
  // Set the cache value. This must happen after removeItemsMatchingService:account:handler was
203
  // called, so the cache value was reset before setting a new value.
204
  if (_cachedKeychainData[service]) {
205
    if (_cachedKeychainData[service][account]) {
206
      _cachedKeychainData[service][account] = @[ data ];
207
    } else {
208
      [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
209
    }
210
  } else {
211
    [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
212
  }
213
}
214
 
215
- (void)setCacheData:(NSData *)data forService:(NSString *)service account:(NSString *)account {
216
  if (_cachedKeychainData[service]) {
217
    if (_cachedKeychainData[service][account]) {
218
      _cachedKeychainData[service][account] = @[ data ];
219
    } else {
220
      [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
221
    }
222
  } else {
223
    [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
224
  }
225
}
226
 
227
@end