]>
Commit | Line | Data |
---|---|---|
ecaf5866 A |
1 | /* |
2 | * Copyright (c) 2017 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | #if OCTAGON | |
24 | ||
25 | #import "OTContext.h" | |
26 | #import "SFPublicKey+SPKI.h" | |
27 | ||
28 | #include <utilities/SecFileLocations.h> | |
29 | #include <Security/SecRandomP.h> | |
30 | ||
31 | #import "keychain/ckks/CKKS.h" | |
32 | #import "keychain/ckks/CKKSViewManager.h" | |
33 | #import "keychain/ckks/CKKSAnalytics.h" | |
34 | ||
35 | #import "CoreCDP/CDPFollowUpController.h" | |
36 | #import "CoreCDP/CDPFollowUpContext.h" | |
37 | #import <CoreCDP/CDPAccount.h> | |
38 | ||
39 | NSString* OTCKContainerName = @"com.apple.security.keychain"; | |
40 | NSString* OTCKZoneName = @"OctagonTrust"; | |
41 | static NSString* const kOTRampZoneName = @"metadata_zone"; | |
42 | ||
43 | @interface OTContext (lockstateTracker) <CKKSLockStateNotification> | |
44 | @end | |
45 | ||
46 | @interface OTContext () | |
47 | ||
48 | @property (nonatomic, strong) NSString* contextID; | |
49 | @property (nonatomic, strong) NSString* contextName; | |
50 | @property (nonatomic, strong) NSString* dsid; | |
51 | ||
52 | @property (nonatomic, strong) OTLocalStore* localStore; | |
53 | @property (nonatomic, strong) OTCloudStore* cloudStore; | |
54 | @property (nonatomic, strong) NSData* changeToken; | |
55 | @property (nonatomic, strong) NSString* egoPeerID; | |
56 | @property (nonatomic, strong) NSDate* egoPeerCreationDate; | |
57 | @property (nonatomic, strong) dispatch_queue_t queue; | |
58 | @property (nonatomic, weak) id <OTContextIdentityProvider> identityProvider; | |
59 | ||
60 | @property (nonatomic, strong) CKKSCKAccountStateTracker* accountTracker; | |
61 | @property (nonatomic, strong) CKKSLockStateTracker* lockStateTracker; | |
62 | @property (nonatomic, strong) CKKSReachabilityTracker *reachabilityTracker; | |
63 | ||
64 | @end | |
65 | ||
66 | @implementation OTContext | |
67 | ||
68 | -(CKContainer*)makeCKContainer:(NSString*)containerName { | |
69 | CKContainer* container = [CKContainer containerWithIdentifier:containerName]; | |
70 | container = [[CKContainer alloc] initWithContainerID: container.containerID]; | |
71 | return container; | |
72 | } | |
73 | ||
74 | -(BOOL) isPrequeliteEnabled | |
75 | { | |
76 | BOOL result = YES; | |
77 | if([PQLConnection class] == nil) { | |
78 | secerror("OT: prequelite appears to not be linked. Can't create OT objects."); | |
79 | result = NO; | |
80 | } | |
81 | return result; | |
82 | } | |
83 | ||
84 | - (nullable instancetype) initWithContextID:(NSString*)contextID | |
85 | dsid:(NSString*)dsid | |
86 | localStore:(OTLocalStore*)localStore | |
87 | cloudStore:(nullable OTCloudStore*)cloudStore | |
88 | identityProvider:(id <OTContextIdentityProvider>)identityProvider | |
89 | error:(NSError**)error | |
90 | { | |
91 | if(![self isPrequeliteEnabled]){ | |
92 | // We're running in the base build environment, which lacks a bunch of libraries. | |
93 | // We don't support doing anything in this environment. Bye. | |
94 | return nil; | |
95 | } | |
96 | ||
97 | self = [super init]; | |
98 | if (self) { | |
99 | NSError* localError = nil; | |
100 | _contextID = contextID; | |
101 | _dsid = dsid; | |
102 | _identityProvider = identityProvider; | |
103 | _localStore = localStore; | |
104 | ||
105 | NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", contextID, dsid]; | |
106 | ||
107 | CKContainer* container = [self makeCKContainer:OTCKContainerName]; | |
108 | ||
109 | _accountTracker = [CKKSViewManager manager].accountTracker; | |
110 | _lockStateTracker = [CKKSViewManager manager].lockStateTracker; | |
111 | _reachabilityTracker = [CKKSViewManager manager].reachabilityTracker; | |
112 | ||
113 | if(!cloudStore) { | |
114 | _cloudStore = [[OTCloudStore alloc]initWithContainer:container | |
115 | zoneName:OTCKZoneName | |
116 | accountTracker:_accountTracker | |
117 | reachabilityTracker:_reachabilityTracker | |
118 | localStore:_localStore | |
119 | contextID:contextID | |
120 | dsid:dsid | |
121 | fetchRecordZoneChangesOperationClass:[CKFetchRecordZoneChangesOperation class] | |
122 | fetchRecordsOperationClass:[CKFetchRecordsOperation class] | |
123 | queryOperationClass:[CKQueryOperation class] | |
124 | modifySubscriptionsOperationClass:[CKModifySubscriptionsOperation class] | |
125 | modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class] | |
126 | apsConnectionClass:[APSConnection class] | |
127 | operationQueue:nil]; | |
128 | } else{ | |
129 | _cloudStore = cloudStore; | |
130 | } | |
131 | ||
132 | OTContextRecord* localContextRecord = [_localStore readLocalContextRecordForContextIDAndDSID:contextAndDSID error:&localError]; | |
133 | ||
134 | if(localContextRecord == nil || localContextRecord.contextID == nil){ | |
135 | localError = nil; | |
136 | BOOL result = [_localStore initializeContextTable:contextID dsid:dsid error:&localError]; | |
137 | if(!result || localError != nil){ | |
138 | secerror("octagon: reading from database failed with error: %@", localError); | |
139 | if (error) { | |
140 | *error = localError; | |
141 | } | |
142 | return nil; | |
143 | } | |
144 | localContextRecord = [_localStore readLocalContextRecordForContextIDAndDSID:contextAndDSID error:&localError]; | |
145 | if(localContextRecord == nil || localError !=nil){ | |
146 | secerror("octagon: reading from database failed with error: %@", localError); | |
147 | if (error) { | |
148 | *error = localError; | |
149 | } | |
150 | return nil; | |
151 | } | |
152 | } | |
153 | ||
154 | _contextID = localContextRecord.contextID; | |
155 | _contextName = localContextRecord.contextName; | |
156 | _changeToken = localContextRecord.changeToken; | |
157 | _egoPeerID = localContextRecord.egoPeerID; | |
158 | _egoPeerCreationDate = localContextRecord.egoPeerCreationDate; | |
159 | ||
160 | _queue = dispatch_queue_create("com.apple.security.otcontext", DISPATCH_QUEUE_SERIAL); | |
161 | } | |
162 | return self; | |
163 | } | |
164 | ||
165 | - (nullable OTBottledPeerSigned *) createBottledPeerRecordForIdentity:(OTIdentity *)identity | |
166 | secret:(NSData*)secret | |
167 | error:(NSError**)error | |
168 | { | |
169 | NSError* localError = nil; | |
170 | if(self.lockStateTracker.isLocked){ | |
171 | secnotice("octagon", "device is locked"); | |
172 | if(error){ | |
173 | *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil]; | |
174 | } | |
175 | return nil; | |
176 | } | |
177 | ||
178 | OTEscrowKeys *escrowKeys = [[OTEscrowKeys alloc] initWithSecret:secret dsid:self.dsid error:&localError]; | |
179 | if (!escrowKeys || localError != nil) { | |
180 | secerror("octagon: unable to derive escrow keys: %@", localError); | |
181 | if (error) { | |
182 | *error = localError; | |
183 | } | |
184 | return nil; | |
185 | } | |
186 | ||
187 | OTBottledPeer *bp = [[OTBottledPeer alloc] initWithPeerID:identity.peerID | |
188 | spID:identity.spID | |
189 | peerSigningKey:identity.peerSigningKey | |
190 | peerEncryptionKey:identity.peerEncryptionKey | |
191 | escrowKeys:escrowKeys | |
192 | error:&localError]; | |
193 | if (!bp || localError !=nil) { | |
194 | secerror("octagon: unable to create a bottled peer: %@", localError); | |
195 | if (error) { | |
196 | *error = localError; | |
197 | } | |
198 | return nil; | |
199 | } | |
200 | return [[OTBottledPeerSigned alloc] initWithBottledPeer:bp | |
201 | escrowedSigningKey:escrowKeys.signingKey | |
202 | peerSigningKey:identity.peerSigningKey | |
203 | error:error]; | |
204 | } | |
205 | ||
206 | - (NSData* _Nullable) makeMeSomeEntropy:(int)requiredLength | |
207 | { | |
208 | NSMutableData* salt = [NSMutableData dataWithLength:requiredLength]; | |
209 | if (salt == nil){ | |
210 | return nil; | |
211 | } | |
212 | if (SecRandomCopyBytes(kSecRandomDefault, [salt length], [salt mutableBytes]) != 0){ | |
213 | return nil; | |
214 | } | |
215 | return salt; | |
216 | } | |
217 | ||
218 | - (nullable OTPreflightInfo*) preflightBottledPeer:(NSString*)contextID | |
219 | entropy:(NSData*)entropy | |
220 | error:(NSError**)error | |
221 | { | |
222 | NSError* localError = nil; | |
223 | if(self.lockStateTracker.isLocked){ | |
224 | secnotice("octagon", "device is locked"); | |
225 | if(error){ | |
226 | *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil]; | |
227 | } | |
228 | return nil; | |
229 | } | |
230 | ||
231 | OTIdentity *identity = [self.identityProvider currentIdentity:&localError]; | |
232 | if (!identity || localError != nil) { | |
233 | secerror("octagon: unable to get current identity:%@", localError); | |
234 | if (error) { | |
235 | *error = localError; | |
236 | } | |
237 | return nil; | |
238 | } | |
239 | ||
240 | OTBottledPeerSigned* bps = [self createBottledPeerRecordForIdentity:identity | |
241 | secret:entropy | |
242 | error:&localError]; | |
243 | if (!bps || localError != nil) { | |
244 | secerror("octagon: failed to create bottled peer record: %@", localError); | |
245 | if (error) { | |
246 | *error = localError; | |
247 | } | |
248 | return nil; | |
249 | } | |
250 | secnotice("octagon", "created bottled peer:%@", bps); | |
251 | ||
252 | OTBottledPeerRecord *bprec = [bps asRecord:identity.spID]; | |
253 | ||
254 | if (!identity.spID) { | |
255 | secerror("octagon: cannot enroll without a spID"); | |
256 | if(error){ | |
257 | *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorNoIdentity userInfo:@{NSLocalizedDescriptionKey: @"OTIdentity does not have an SOS peer id"}]; | |
258 | } | |
259 | return nil; | |
260 | } | |
261 | ||
262 | OTPreflightInfo* info = [[OTPreflightInfo alloc]init]; | |
263 | info.escrowedSigningSPKI = bprec.escrowedSigningSPKI; | |
264 | ||
265 | if(!info.escrowedSigningSPKI){ | |
266 | if(error){ | |
267 | *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEscrowSigningSPKI userInfo:@{NSLocalizedDescriptionKey: @"Escrowed spinging SPKI is nil"}]; | |
268 | } | |
269 | secerror("octagon: Escrowed spinging SPKI is nil"); | |
270 | return nil; | |
271 | } | |
272 | ||
273 | info.bottleID = bprec.recordName; | |
274 | if(!info.bottleID){ | |
275 | if(error){ | |
276 | *error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorBottleID userInfo:@{NSLocalizedDescriptionKey: @"BottleID is nil"}]; | |
277 | } | |
278 | secerror("octagon: BottleID is nil"); | |
279 | return nil; | |
280 | } | |
281 | ||
282 | //store record in localStore | |
283 | BOOL result = [self.localStore insertBottledPeerRecord:bprec escrowRecordID:identity.spID error:&localError]; | |
284 | if(!result || localError){ | |
285 | secerror("octagon: could not persist the bottle record: %@", localError); | |
286 | if (error) { | |
287 | *error = localError; | |
288 | } | |
289 | return nil; | |
290 | } | |
291 | ||
292 | return info; | |
293 | } | |
294 | ||
295 | - (BOOL)scrubBottledPeer:(NSString*)contextID | |
296 | bottleID:(NSString*)bottleID | |
297 | error:(NSError**)error | |
298 | { | |
299 | secnotice("octagon", "scrubBottledPeer"); | |
300 | NSError* localError = nil; | |
301 | if(self.lockStateTracker.isLocked){ | |
302 | secnotice("octagon", "device is locked"); | |
303 | if(error){ | |
304 | *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil]; | |
305 | } | |
306 | return YES; | |
307 | } | |
308 | ||
309 | BOOL result = [self.localStore deleteBottledPeer:bottleID error:&localError]; | |
310 | if(!result || localError != nil){ | |
311 | secerror("octagon: could not remove record for bottleID %@, error:%@", bottleID, localError); | |
312 | if (error) { | |
313 | *error = localError; | |
314 | } | |
315 | } | |
316 | return result; | |
317 | } | |
318 | ||
319 | - (OTBottledPeerSigned *) restoreFromEscrowRecordID:(NSString*)escrowRecordID | |
320 | secret:(NSData*)secret | |
321 | error:(NSError**)error | |
322 | { | |
323 | NSError *localError = nil; | |
324 | ||
325 | if(self.lockStateTracker.isLocked){ | |
326 | if(error){ | |
327 | *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil]; | |
328 | } | |
329 | return nil; | |
330 | } | |
331 | ||
332 | OTEscrowKeys *escrowKeys = [[OTEscrowKeys alloc] initWithSecret:secret dsid:self.dsid error:&localError]; | |
333 | if (!escrowKeys || localError != nil) { | |
334 | secerror("unable to derive escrow keys: %@", localError); | |
335 | if (error) { | |
336 | *error = localError; | |
337 | } | |
338 | return nil; | |
339 | } | |
340 | ||
341 | BOOL result = [self.cloudStore downloadBottledPeerRecord:&localError]; | |
342 | if(!result || localError){ | |
343 | secerror("octagon: could not download bottled peer record:%@", localError); | |
344 | if(error){ | |
345 | *error = localError; | |
346 | } | |
347 | } | |
348 | NSString* recordName = [OTBottledPeerRecord constructRecordID:escrowRecordID | |
349 | escrowSigningSPKI:[escrowKeys.signingKey.publicKey asSPKI]]; | |
350 | OTBottledPeerRecord* rec = [self.localStore readLocalBottledPeerRecordWithRecordID:recordName error:&localError]; | |
351 | ||
352 | if (!rec) { | |
353 | secerror("octagon: could not read bottled peer record:%@", localError); | |
354 | if (error) { | |
355 | *error = localError; | |
356 | } | |
357 | return nil; | |
358 | } | |
359 | ||
360 | OTBottledPeerSigned *bps = [[OTBottledPeerSigned alloc] initWithBottledPeerRecord:rec | |
361 | escrowKeys:escrowKeys | |
362 | error:&localError]; | |
363 | if (!bps) { | |
364 | secerror("octagon: could not unpack bottled peer:%@", localError); | |
365 | if (error) { | |
366 | *error = localError; | |
367 | } | |
368 | return nil; | |
369 | } | |
370 | ||
371 | return bps; | |
372 | } | |
373 | ||
374 | -(BOOL)bottleExistsLocallyForIdentity:(OTIdentity*)identity logger:(CKKSAnalytics*)logger error:(NSError**)error | |
375 | { | |
376 | NSError* localError = nil; | |
377 | //read all the local bp records | |
378 | NSArray<OTBottledPeerRecord*>* bottles = [self.localStore readLocalBottledPeerRecordsWithMatchingPeerID:identity.spID error:&localError]; | |
379 | if(!bottles || [bottles count] == 0 || localError != nil){ | |
380 | secerror("octagon: there are no eligible bottle peer records: %@", localError); | |
381 | [logger logRecoverableError:localError | |
382 | forEvent:OctagonEventBottleCheck | |
383 | zoneName:kOTRampZoneName | |
384 | withAttributes:NULL]; | |
385 | if(error){ | |
386 | *error = localError; | |
387 | } | |
388 | return NO; | |
389 | } | |
390 | ||
391 | BOOL hasBottle = NO; | |
392 | //if check all the records if the peer signing public key matches the bottled one! | |
393 | for(OTBottledPeerRecord* bottle in bottles){ | |
394 | NSData* bottledSigningSPKIData = [[SFECPublicKey fromSPKI:bottle.peerSigningSPKI] keyData]; | |
395 | NSData* currentIdentitySPKIData = [identity.peerSigningKey.publicKey keyData]; | |
396 | ||
397 | //spIDs are the same AND check bottle signature | |
398 | if([currentIdentitySPKIData isEqualToData:bottledSigningSPKIData] && | |
399 | [OTBottledPeerSigned verifyBottleSignature:bottle.bottle | |
400 | signature:bottle.signatureUsingPeerKey | |
401 | key:identity.peerSigningKey.publicKey | |
402 | error:error]){ | |
403 | hasBottle = YES; | |
404 | } | |
405 | } | |
406 | ||
407 | ||
408 | ||
409 | return hasBottle; | |
410 | } | |
411 | ||
412 | -(BOOL)queryCloudKitForBottle:(OTIdentity*)identity logger:(CKKSAnalytics*)logger error:(NSError**)error | |
413 | { | |
414 | NSError* localError = nil; | |
415 | BOOL hasBottle = NO; | |
416 | //attempt to pull down all the records, but continue checking local store even if this fails. | |
417 | BOOL fetched = [self.cloudStore downloadBottledPeerRecord:&localError]; | |
418 | if(fetched == NO || localError != nil){ //couldn't download bottles | |
419 | secerror("octagon: 0 bottled peers downloaded: %@", localError); | |
420 | [logger logRecoverableError:localError | |
421 | forEvent:OctagonEventBottleCheck | |
422 | zoneName:kOTRampZoneName | |
423 | withAttributes:NULL]; | |
424 | if(error){ | |
425 | *error = localError; | |
426 | } | |
427 | return NO; | |
428 | }else{ //downloaded bottles, let's check local store | |
429 | hasBottle = [self bottleExistsLocallyForIdentity:identity logger:logger error:&localError]; | |
430 | } | |
431 | ||
432 | if(error){ | |
433 | *error = localError; | |
434 | } | |
435 | return hasBottle; | |
436 | } | |
437 | ||
438 | -(OctagonBottleCheckState) doesThisDeviceHaveABottle:(NSError**)error | |
439 | { | |
440 | secnotice("octagon", "checking if device has enrolled a bottle"); | |
441 | ||
442 | if(self.lockStateTracker.isLocked){ | |
443 | secnotice("octagon", "device locked, not checking for bottle"); | |
444 | if(error){ | |
445 | *error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:nil]; | |
446 | } | |
447 | return UNCLEAR; | |
448 | } | |
449 | ||
450 | if(self.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){ | |
451 | if(error){ | |
452 | *error = [NSError errorWithDomain:octagonErrorDomain | |
453 | code:OTErrorNotSignedIn | |
454 | userInfo:@{NSLocalizedDescriptionKey: @"iCloud account is logged out"}]; | |
455 | } | |
456 | secnotice("octagon", "not logged into an account"); | |
457 | return UNCLEAR; | |
458 | } | |
459 | ||
460 | NSError* localError = nil; | |
461 | OctagonBottleCheckState bottleStatus = NOBOTTLE; | |
462 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
463 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityBottleCheck withAction:nil]; | |
464 | [tracker start]; | |
465 | ||
466 | //get our current identity | |
467 | OTIdentity* identity = [self.identityProvider currentIdentity:&localError]; | |
468 | ||
469 | //if we get the locked error, return true so we don't prompt the user | |
470 | if(localError && [_lockStateTracker isLockedError:localError]){ | |
471 | secnotice("octagon", "attempting to perform bottle check while locked: %@", localError); | |
472 | return UNCLEAR; | |
473 | } | |
474 | ||
475 | if(!identity && localError != nil){ | |
476 | secerror("octagon: do not have an identity: %@", localError); | |
477 | [logger logRecoverableError:localError | |
478 | forEvent:OctagonEventBottleCheck | |
479 | zoneName:kOTRampZoneName | |
480 | withAttributes:NULL]; | |
481 | [tracker stop]; | |
482 | if(error){ | |
483 | *error = localError; | |
484 | } | |
485 | return NOBOTTLE; | |
486 | } | |
487 | ||
488 | //check locally first | |
489 | BOOL bottleExistsLocally = [self bottleExistsLocallyForIdentity:identity logger:logger error:&localError]; | |
490 | ||
491 | //no bottle and we have no network | |
492 | if(!bottleExistsLocally && !self.reachabilityTracker.currentReachability){ | |
493 | secnotice("octagon", "no network, can't query"); | |
494 | localError = [NSError errorWithDomain:octagonErrorDomain | |
495 | code:OTErrorNoNetwork | |
496 | userInfo:@{NSLocalizedDescriptionKey: @"no network"}]; | |
497 | [tracker stop]; | |
498 | if(error){ | |
499 | *error = localError; | |
500 | } | |
501 | return UNCLEAR; | |
502 | } | |
503 | else if(!bottleExistsLocally){ | |
504 | if([self queryCloudKitForBottle:identity logger:logger error:&localError]){ | |
505 | bottleStatus = BOTTLE; | |
506 | } | |
507 | }else if(bottleExistsLocally){ | |
508 | bottleStatus = BOTTLE; | |
509 | } | |
510 | ||
511 | if(bottleStatus == NOBOTTLE){ | |
512 | localError = [NSError errorWithDomain:octagonErrorDomain code:OTErrorNoBottlePeerRecords userInfo:@{NSLocalizedDescriptionKey: @"Peer %@ does not have any bottled records"}]; | |
513 | secerror("octagon: this device does not have any bottled peers: %@", localError); | |
514 | [logger logRecoverableError:localError | |
515 | forEvent:OctagonEventBottleCheck | |
516 | zoneName:kOTRampZoneName | |
517 | withAttributes:@{ OctagonEventAttributeFailureReason : @"does not have bottle"}]; | |
518 | if(error){ | |
519 | *error = localError; | |
520 | } | |
521 | } | |
522 | else{ | |
523 | [logger logSuccessForEventNamed:OctagonEventBottleCheck]; | |
524 | } | |
525 | ||
526 | [tracker stop]; | |
527 | ||
528 | return bottleStatus; | |
529 | } | |
530 | ||
531 | -(void) postFollowUp | |
532 | { | |
533 | NSError* error = nil; | |
534 | ||
535 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
536 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityBottleCheck withAction:nil]; | |
537 | ||
538 | [tracker start]; | |
539 | CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; | |
540 | CDPFollowUpContext *context = [CDPFollowUpContext contextForOfflinePasscodeChange]; | |
541 | ||
542 | [cdpd postFollowUpWithContext:context error:&error]; | |
543 | if(error){ | |
544 | [logger logUnrecoverableError:error forEvent:OctagonEventCoreFollowUp withAttributes:@{ | |
545 | OctagonEventAttributeFailureReason : @"core follow up failed"}]; | |
546 | ||
547 | secerror("request to CoreCDP to follow up failed: %@", error); | |
548 | } | |
549 | else{ | |
550 | [logger logSuccessForEventNamed:OctagonEventCoreFollowUp]; | |
551 | } | |
552 | [tracker stop]; | |
553 | } | |
554 | ||
555 | ||
556 | @end | |
557 | #endif |