]> git.saurik.com Git - apple/security.git/blob - keychain/SigninMetrics/SFSignInAnalytics.m
Security-59306.140.5.tar.gz
[apple/security.git] / keychain / SigninMetrics / SFSignInAnalytics.m
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
24 #if __OBJC2__
25
26 #import "SFSignInAnalytics.h"
27 #import "SFSignInAnalytics+Internal.h"
28
29 #import <Analytics/SFAnalytics+Signin.h>
30 #import "Analytics/SFAnalyticsDefines.h"
31 #import "Analytics/SFAnalyticsSQLiteStore.h"
32 #import "Analytics/SFAnalytics.h"
33
34 #import <os/log_private.h>
35 #import <mach/mach_time.h>
36 #import <utilities/SecFileLocations.h>
37 #import "utilities/debugging.h"
38 #import <utilities/SecCFWrappers.h>
39
40 //metrics database location
41 NSString* signinMetricsDatabase = @"signin_metrics";
42
43 //defaults write results location
44 static NSString* const SFSignInAnalyticsDumpLoggedResultsToLocation = @"/tmp/signin_results.txt";
45 static NSString* const SFSignInAnalyticsPersistedEventList = @"/tmp/signin_eventlist";
46
47 //analytics constants
48 static NSString* const SFSignInAnalyticsAttributeRecoverableError = @"recoverableError";
49 static NSString* const SFSignInAnalyticsAttributeErrorDomain = @"errorDomain";
50 static NSString* const SFSignInAnalyticsAttributeErrorCode = @"errorCode";
51 static NSString* const SFSignInAnalyticsAttributeErrorChain = @"errorChain";
52 static NSString* const SFSignInAnalyticsAttributeParentUUID = @"parentUUID";
53 static NSString* const SFSignInAnalyticsAttributeMyUUID = @"myUUID";
54 static NSString* const SFSignInAnalyticsAttributeSignInUUID = @"signinUUID";
55 static NSString* const SFSignInAnalyticsAttributeEventName = @"eventName";
56 static NSString* const SFSignInAnalyticsAttributeSubsystemName = @"subsystemName";
57 static NSString* const SFSignInAnalyticsAttributeBuiltDependencyChains = @"dependencyChains";
58 static NSString* const SFSignInAnalyticsAttributeTrackerTime = @"trackerTime";
59
60 @implementation SFSIALoggerObject
61 + (NSString*)databasePath {
62 return [SFSIALoggerObject defaultAnalyticsDatabasePath:signinMetricsDatabase];
63 }
64
65 + (instancetype)logger
66 {
67 return [super logger];
68 }
69 @end
70
71
72 @interface SFSignInAnalytics ()
73 @property (nonatomic, copy) NSString *signin_uuid;
74 @property (nonatomic, copy) NSString *my_uuid;
75 @property (nonatomic, copy) NSString *parent_uuid;
76 @property (nonatomic, copy) NSString *category;
77 @property (nonatomic, copy) NSString *eventName;
78 @property (nonatomic, copy) NSString *persistencePath;
79
80 @property (nonatomic, strong) NSURL *persistedEventPlist;
81 @property (nonatomic, strong) NSMutableDictionary *eventDependencyList;
82 @property (nonatomic, strong) NSMutableArray *builtDependencyChains;
83
84 @property (nonatomic) BOOL canceled;
85 @property (nonatomic) BOOL stopped;
86
87 @property (nonatomic, strong) os_log_t logObject;
88
89 @property (nonatomic, strong) NSNumber *measurement;
90
91 @property (nonatomic, strong) dispatch_queue_t queue;
92
93 @property (nonatomic, strong) SFSignInAnalytics *root;
94 @property (nonatomic, strong) SFAnalyticsActivityTracker *tracker;
95
96 -(os_log_t) newLogForCategoryName:(NSString*) category;
97 -(os_log_t) logForCategoryName:(NSString*) category;
98
99 @end
100
101 static NSMutableDictionary *logObjects;
102 static const NSString* signInLogSpace = @"com.apple.security.wiiss";
103
104 @implementation SFSignInAnalytics
105
106 + (BOOL)supportsSecureCoding {
107 return YES;
108 }
109
110 -(os_log_t) logForCategoryName:(NSString*) category
111 {
112 return logObjects[category];
113 }
114
115 -(os_log_t) newLogForCategoryName:(NSString*) category
116 {
117 return os_log_create([signInLogSpace UTF8String], [category UTF8String]);
118 }
119
120 - (BOOL)writeDependencyList:(NSError**)error
121 {
122 NSError *localError = nil;
123 if (![NSPropertyListSerialization propertyList: self.root.eventDependencyList isValidForFormat: NSPropertyListXMLFormat_v1_0]){
124 os_log_error(self.logObject, "can't save PersistentState as XML");
125 return false;
126 }
127
128 NSData *data = [NSPropertyListSerialization dataWithPropertyList: self.root.eventDependencyList
129 format: NSPropertyListXMLFormat_v1_0 options: 0 error: &localError];
130 if (data == nil){
131 os_log_error(self.logObject, "error serializing PersistentState to xml: %@", localError);
132 return false;
133 }
134
135 BOOL writeStatus = [data writeToURL:self.root.persistedEventPlist options: NSDataWritingAtomic error: &localError];
136 if (!writeStatus){
137 os_log_error(self.logObject, "error writing PersistentState to file: %@", localError);
138 }
139 if(localError && error){
140 *error = localError;
141 }
142
143 return writeStatus;
144 }
145
146 - (instancetype)initWithSignInUUID:(NSString *)uuid category:(NSString *)category eventName:(NSString*)eventName
147 {
148 self = [super init];
149 if (self) {
150 _signin_uuid = uuid;
151
152 _my_uuid = uuid;
153 _parent_uuid = uuid;
154 _eventName = eventName;
155 _category = category;
156 _root = self;
157 _canceled = NO;
158 _stopped = NO;
159 _builtDependencyChains = [NSMutableArray array];
160
161 if ([self writeResultsToTmp]) {
162 //make plist file containing uuid parent/child
163 _persistencePath = [NSString stringWithFormat:@"%@-%@.plist", SFSignInAnalyticsPersistedEventList, eventName];
164 _persistedEventPlist = [NSURL fileURLWithPath:_persistencePath isDirectory:NO];
165 }
166
167 _eventDependencyList = [NSMutableDictionary dictionary];
168 [_eventDependencyList setObject:[NSMutableArray array] forKey:_signin_uuid];
169
170 _tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
171 [_tracker start];
172
173 NSError* error = nil;
174
175 if(self.root.persistedEventPlist && ![self writeDependencyList:&error] ){
176 os_log(self.logObject, "attempting to write dependency list: %@", error);
177 }
178
179 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
180
181 static dispatch_once_t onceToken;
182 dispatch_once(&onceToken, ^{
183 logObjects = [NSMutableDictionary dictionary];
184 });
185 @synchronized(logObjects){
186 if(category){
187 _logObject = [self logForCategoryName:category];
188
189 if(!_logObject){
190 _logObject = [self newLogForCategoryName:category];
191 [logObjects setObject:_logObject forKey:category];
192 }
193 }
194 }
195 }
196 return self;
197 }
198
199 -(instancetype) initChildWithSignInUUID:(NSString*)uuid andCategory:(NSString*)category andEventName:(NSString*)eventName
200 {
201 self = [super init];
202 if (self) {
203 _signin_uuid = uuid;
204
205 _my_uuid = uuid;
206 _parent_uuid = uuid;
207 _eventName = eventName;
208 _category = category;
209 _canceled = NO;
210 }
211 return self;
212 }
213
214 - (void)encodeWithCoder:(NSCoder *)coder {
215 [coder encodeObject:_signin_uuid forKey:@"UUID"];
216 [coder encodeObject:_category forKey:@"category"];
217 [coder encodeObject:_parent_uuid forKey:@"parentUUID"];
218 [coder encodeObject:_my_uuid forKey:@"myUUID"];
219 [coder encodeObject:_measurement forKey:@"measurement"];
220 [coder encodeObject:_eventName forKey:@"eventName"];
221 }
222
223 - (nullable instancetype)initWithCoder:(NSCoder *)decoder
224 {
225 self = [super init];
226 if (self) {
227 _signin_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"UUID"];
228 _category = [decoder decodeObjectOfClass:[NSString class] forKey:@"category"];
229 _parent_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentUUID"];
230 _my_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"myUUID"];
231 _measurement = [decoder decodeObjectOfClass:[NSString class] forKey:@"measurement"];
232 _eventName = [decoder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
233 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
234
235 if(_signin_uuid == nil ||
236 _category == nil ||
237 _parent_uuid == nil){
238 [decoder failWithError:[NSError errorWithDomain:@"securityd" code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Failed to decode SignInAnalytics object"}]];
239 return nil;
240 }
241 }
242 return self;
243 }
244
245 - (SFSignInAnalytics*)newSubTaskForEvent:(NSString*)eventName
246 {
247 SFSignInAnalytics *newSubTask = [[SFSignInAnalytics alloc] initChildWithSignInUUID:self.signin_uuid andCategory:self.category andEventName:self.eventName];
248 if(newSubTask){
249 newSubTask.my_uuid = [NSUUID UUID].UUIDString;
250 newSubTask.parent_uuid = self.my_uuid;
251 newSubTask.signin_uuid = self.signin_uuid;
252
253 newSubTask.category = self.category;
254 newSubTask.eventName = [eventName copy];
255 newSubTask.root = self.root;
256 newSubTask.canceled = NO;
257 newSubTask.stopped = NO;
258
259 newSubTask.queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
260 newSubTask.tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
261 [newSubTask.tracker start];
262
263 @synchronized(_eventDependencyList){
264 NSMutableArray *parentEntry = [newSubTask.root.eventDependencyList objectForKey:newSubTask.parent_uuid];
265
266 //add new subtask entry to parent event's list
267 [parentEntry addObject:newSubTask.my_uuid];
268 [newSubTask.root.eventDependencyList setObject:parentEntry forKey:newSubTask.parent_uuid];
269
270 //create new array list for this new subtask incase it has subtasks
271 [newSubTask.root.eventDependencyList setObject:[NSMutableArray array] forKey:newSubTask.my_uuid];
272 NSError* error = nil;
273 if(self.root.persistedEventPlist && ![newSubTask writeDependencyList:&error] ){
274 os_log(self.logObject, "attempting to write dependency list: %@", error);
275 }
276 }
277 }
278
279 return newSubTask;
280 }
281
282 - (void)logRecoverableError:(NSError*)error
283 {
284
285 if (error == nil){
286 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
287 return;
288 }
289
290 os_log_error(self.logObject, "%@", error);
291
292 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
293
294 [eventAttributes setValuesForKeysWithDictionary:@{
295 SFSignInAnalyticsAttributeRecoverableError : @(YES),
296 SFSignInAnalyticsAttributeErrorDomain : error.domain,
297 SFSignInAnalyticsAttributeErrorCode : @(error.code),
298 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
299 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
300 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
301 SFSignInAnalyticsAttributeEventName : self.eventName,
302 SFSignInAnalyticsAttributeSubsystemName : self.category
303 }];
304
305 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:eventAttributes];
306
307 }
308
309 - (void)logUnrecoverableError:(NSError*)error
310 {
311 if (error == nil){
312 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
313 return;
314 }
315
316 os_log_error(self.logObject, "%@", error);
317
318 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
319
320 [eventAttributes setValuesForKeysWithDictionary:@{
321 SFSignInAnalyticsAttributeRecoverableError : @(NO),
322 SFSignInAnalyticsAttributeErrorDomain : error.domain,
323 SFSignInAnalyticsAttributeErrorCode : @(error.code),
324 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
325 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
326 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
327 SFSignInAnalyticsAttributeEventName : self.eventName,
328 SFSignInAnalyticsAttributeSubsystemName : self.category
329 }];
330
331 [[SFSIALoggerObject logger] logHardFailureForEventNamed:self.eventName withAttributes:eventAttributes];
332 }
333
334 -(void)cancel
335 {
336 dispatch_sync(self.queue, ^{
337 [self.tracker cancel];
338 self.canceled = YES;
339 os_log(self.logObject, "canceled timer for %@", self.eventName);
340 });
341 }
342
343 - (void)stopWithAttributes:(NSDictionary<NSString*, id>*)attributes
344 {
345 dispatch_sync(self.queue, ^{
346
347 if(self.canceled || self.stopped){
348 return;
349 }
350
351 self.stopped = YES;
352
353 [self.tracker stop];
354
355 NSMutableDictionary *mutableAttributes = nil;
356
357 if(attributes){
358 mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
359 }
360 else{
361 mutableAttributes = [NSMutableDictionary dictionary];
362 }
363 mutableAttributes[SFSignInAnalyticsAttributeMyUUID] = self.my_uuid;
364 mutableAttributes[SFSignInAnalyticsAttributeParentUUID] = self.parent_uuid;
365 mutableAttributes[SFSignInAnalyticsAttributeSignInUUID] = self.signin_uuid;
366 mutableAttributes[SFSignInAnalyticsAttributeEventName] = self.eventName;
367 mutableAttributes[SFSignInAnalyticsAttributeSubsystemName] = self.category;
368 mutableAttributes[SFSignInAnalyticsAttributeTrackerTime] = self.tracker.measurement;
369
370 [mutableAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL * stop) {
371 os_log(self.logObject, "event: %@, %@ : %@", self.eventName, key, obj);
372 }];
373
374 [[SFSIALoggerObject logger] logSuccessForEventNamed:self.eventName];
375 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:mutableAttributes];
376 });
377 }
378
379 -(BOOL) writeResultsToTmp {
380
381 bool shouldWriteResultsToTemp = NO;
382 CFBooleanRef toTmp = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("DumpResultsToTemp"),
383 CFSTR("com.apple.security"),
384 kCFPreferencesAnyUser, kCFPreferencesAnyHost);
385 if(toTmp && CFGetTypeID(toTmp) == CFBooleanGetTypeID()){
386 if(toTmp == kCFBooleanFalse){
387 os_log(self.logObject, "writing results to splunk");
388 shouldWriteResultsToTemp = NO;
389 }
390 if(toTmp == kCFBooleanTrue){
391 os_log(self.logObject, "writing results to /tmp");
392 shouldWriteResultsToTemp = YES;
393 }
394 }
395
396 CFReleaseNull(toTmp);
397 return shouldWriteResultsToTemp;
398 }
399
400 - (void)processEventChainForUUID:(NSString*)uuid dependencyChain:(NSString*)dependencyChain
401 {
402 NSString* newChain = dependencyChain;
403
404 NSArray* children = [self.root.eventDependencyList objectForKey:uuid];
405 for (NSString* child in children) {
406 newChain = [NSString stringWithFormat:@"%@, %@", dependencyChain, child];
407 [self processEventChainForUUID:child dependencyChain:newChain];
408 }
409 if([children count] == 0){
410 [self.root.builtDependencyChains addObject:newChain];
411 os_log(self.logObject, "current dependency chain list: %@", newChain);
412 }
413 }
414
415 - (void)signInCompleted
416 {
417 //print final
418 os_log(self.logObject, "sign in complete");
419 NSError* error = nil;
420
421 //create dependency chains and log them
422 [self processEventChainForUUID:self.root.my_uuid dependencyChain:self.root.signin_uuid];
423 //write to database
424 if([self.root.builtDependencyChains count] > 0){
425 NSDictionary* eventAttributes = @{SFSignInAnalyticsAttributeBuiltDependencyChains : self.root.builtDependencyChains};
426 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:SFSignInAnalyticsAttributeBuiltDependencyChains withAttributes:eventAttributes];
427 }
428
429 if([self writeResultsToTmp]){ //writing sign in analytics to /tmp
430 os_log(self.logObject, "logging to /tmp");
431
432 NSData* eventData = [NSKeyedArchiver archivedDataWithRootObject:[[SFSIALoggerObject logger].database allEvents] requiringSecureCoding:YES error:&error];
433 if(eventData){
434 [eventData writeToFile:SFSignInAnalyticsDumpLoggedResultsToLocation options:0 error:&error];
435
436 if(error){
437 os_log_error(self.logObject, "error writing to file [%@], error:%@", SFSignInAnalyticsDumpLoggedResultsToLocation, error);
438 }else{
439 os_log(self.logObject, "successfully wrote sign in analytics to:%@", SFSignInAnalyticsDumpLoggedResultsToLocation);
440 }
441 }else{
442 os_log_error(self.logObject, "collected no data");
443 }
444
445 }else{ //writing to splunk
446 os_log(self.logObject, "logging to splunk");
447 }
448
449 if (self.persistencePath) {
450 //remove dependency list
451 BOOL removedPersistedDependencyList = [[NSFileManager defaultManager] removeItemAtPath:self.persistencePath error:&error];
452 if(!removedPersistedDependencyList || error){
453 os_log(self.logObject, "encountered error when attempting to remove persisted event list: %@", error);
454 }
455 }
456 }
457
458 @end
459 #endif
460