SFSignInAnalytics.m [plain text]
/*
* Copyright (c) 2017 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#if __OBJC2__
#import "SFSignInAnalytics.h"
#import "SFSignInAnalytics+Internal.h"
#import <Analytics/SFAnalytics+Signin.h>
#import "SFAnalyticsDefines.h"
#import "SFAnalyticsSQLiteStore.h"
#import "SFAnalytics.h"
#import <os/log_private.h>
#import <mach/mach_time.h>
#import <utilities/SecFileLocations.h>
#import <utilities/debugging.h>
#import <utilities/SecCFWrappers.h>
//metrics database location
NSString* signinMetricsDatabase = @"signin_metrics";
//defaults write results location
static NSString* const SFSignInAnalyticsDumpLoggedResultsToLocation = @"/tmp/signin_results.txt";
static NSString* const SFSignInAnalyticsPersistedEventList = @"/tmp/signin_eventlist";
//analytics constants
static NSString* const SFSignInAnalyticsAttributeRecoverableError = @"recoverableError";
static NSString* const SFSignInAnalyticsAttributeErrorDomain = @"errorDomain";
static NSString* const SFSignInAnalyticsAttributeErrorCode = @"errorCode";
static NSString* const SFSignInAnalyticsAttributeErrorChain = @"errorChain";
static NSString* const SFSignInAnalyticsAttributeParentUUID = @"parentUUID";
static NSString* const SFSignInAnalyticsAttributeMyUUID = @"myUUID";
static NSString* const SFSignInAnalyticsAttributeSignInUUID = @"signinUUID";
static NSString* const SFSignInAnalyticsAttributeEventName = @"eventName";
static NSString* const SFSignInAnalyticsAttributeSubsystemName = @"subsystemName";
static NSString* const SFSignInAnalyticsAttributeBuiltDependencyChains = @"dependencyChains";
@implementation SFSIALoggerObject
+ (NSString*)databasePath {
return [SFSIALoggerObject defaultAnalyticsDatabasePath:signinMetricsDatabase];
}
+ (instancetype)logger
{
return [super logger];
}
@end
@interface SFSignInAnalytics ()
@property (nonatomic, copy) NSString *signin_uuid;
@property (nonatomic, copy) NSString *my_uuid;
@property (nonatomic, copy) NSString *parent_uuid;
@property (nonatomic, copy) NSString *category;
@property (nonatomic, copy) NSString *eventName;
@property (nonatomic, copy) NSString *persistencePath;
@property (nonatomic, strong) NSURL *persistedEventPlist;
@property (nonatomic, strong) NSMutableDictionary *eventDependencyList;
@property (nonatomic, strong) NSMutableArray *builtDependencyChains;
@property (nonatomic) BOOL canceled;
@property (nonatomic) BOOL stopped;
@property (nonatomic, strong) os_log_t logObject;
@property (nonatomic, strong) NSNumber *measurement;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) SFSignInAnalytics *root;
@property (nonatomic, strong) SFAnalyticsActivityTracker *tracker;
-(os_log_t) newLogForCategoryName:(NSString*) category;
-(os_log_t) logForCategoryName:(NSString*) category;
@end
static NSMutableDictionary *logObjects;
static const NSString* signInLogSpace = @"com.apple.security.wiiss";
@implementation SFSignInAnalytics
+ (BOOL)supportsSecureCoding {
return YES;
}
-(os_log_t) logForCategoryName:(NSString*) category
{
return logObjects[category];
}
-(os_log_t) newLogForCategoryName:(NSString*) category
{
return os_log_create([signInLogSpace UTF8String], [category UTF8String]);
}
- (BOOL)writeDependencyList:(NSError**)error
{
NSError *localError = nil;
if (![NSPropertyListSerialization propertyList: self.root.eventDependencyList isValidForFormat: NSPropertyListXMLFormat_v1_0]){
os_log_error(self.logObject, "can't save PersistentState as XML");
return false;
}
NSData *data = [NSPropertyListSerialization dataWithPropertyList: self.root.eventDependencyList
format: NSPropertyListXMLFormat_v1_0 options: 0 error: &localError];
if (data == nil){
os_log_error(self.logObject, "error serializing PersistentState to xml: return false;
}
BOOL writeStatus = [data writeToURL:self.root.persistedEventPlist options: NSDataWritingAtomic error: &localError];
if (!writeStatus){
os_log_error(self.logObject, "error writing PersistentState to file: }
if(localError && error){
*error = localError;
}
return writeStatus;
}
- (instancetype)initWithSignInUUID:(NSString *)uuid category:(NSString *)category eventName:(NSString*)eventName
{
self = [super init];
if (self) {
_signin_uuid = uuid;
_my_uuid = uuid;
_parent_uuid = uuid;
_eventName = eventName;
_category = category;
_root = self;
_canceled = NO;
_stopped = NO;
_builtDependencyChains = [NSMutableArray array];
//make plist file containing uuid parent/child
_persistencePath = [NSString stringWithFormat:@" _persistedEventPlist = [NSURL fileURLWithPath:_persistencePath isDirectory:NO];
_eventDependencyList = [NSMutableDictionary dictionary];
[_eventDependencyList setObject:[NSMutableArray array] forKey:_signin_uuid];
_tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
[_tracker start];
NSError* error = nil;
if(![self writeDependencyList:&error]){
os_log(self.logObject, "attempting to write dependency list: }
_queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
logObjects = [NSMutableDictionary dictionary];
});
@synchronized(logObjects){
if(category){
_logObject = [self logForCategoryName:category];
if(!_logObject){
_logObject = [self newLogForCategoryName:category];
[logObjects setObject:_logObject forKey:category];
}
}
}
}
return self;
}
-(instancetype) initChildWithSignInUUID:(NSString*)uuid andCategory:(NSString*)category andEventName:(NSString*)eventName
{
self = [super init];
if (self) {
_signin_uuid = uuid;
_my_uuid = uuid;
_parent_uuid = uuid;
_eventName = eventName;
_category = category;
_canceled = NO;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_signin_uuid forKey:@"UUID"];
[coder encodeObject:_category forKey:@"category"];
[coder encodeObject:_parent_uuid forKey:@"parentUUID"];
[coder encodeObject:_my_uuid forKey:@"myUUID"];
[coder encodeObject:_measurement forKey:@"measurement"];
[coder encodeObject:_eventName forKey:@"eventName"];
}
- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
_signin_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"UUID"];
_category = [decoder decodeObjectOfClass:[NSString class] forKey:@"category"];
_parent_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentUUID"];
_my_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"myUUID"];
_measurement = [decoder decodeObjectOfClass:[NSString class] forKey:@"measurement"];
_eventName = [decoder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
_queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
if(_signin_uuid == nil ||
_category == nil ||
_parent_uuid == nil){
[decoder failWithError:[NSError errorWithDomain:@"securityd" code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Failed to decode SignInAnalytics object"}]];
return nil;
}
}
return self;
}
- (SFSignInAnalytics*)newSubTaskForEvent:(NSString*)eventName
{
SFSignInAnalytics *newSubTask = [[SFSignInAnalytics alloc] initChildWithSignInUUID:self.signin_uuid andCategory:self.category andEventName:self.eventName];
if(newSubTask){
newSubTask.my_uuid = [NSUUID UUID].UUIDString;
newSubTask.parent_uuid = self.my_uuid;
newSubTask.signin_uuid = self.signin_uuid;
newSubTask.category = self.category;
newSubTask.eventName = [eventName copy];
newSubTask.root = self.root;
newSubTask.canceled = NO;
newSubTask.stopped = NO;
newSubTask.queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
newSubTask.tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
[newSubTask.tracker start];
@synchronized(_eventDependencyList){
NSMutableArray *parentEntry = [newSubTask.root.eventDependencyList objectForKey:newSubTask.parent_uuid];
//add new subtask entry to parent event's list
[parentEntry addObject:newSubTask.my_uuid];
[newSubTask.root.eventDependencyList setObject:parentEntry forKey:newSubTask.parent_uuid];
//create new array list for this new subtask incase it has subtasks
[newSubTask.root.eventDependencyList setObject:[NSMutableArray array] forKey:newSubTask.my_uuid];
NSError* error = nil;
if(![newSubTask writeDependencyList:&error]){
os_log(self.logObject, "attempting to write dependency list: }
}
}
return newSubTask;
}
- (void)logRecoverableError:(NSError*)error
{
if (error == nil){
os_log_error(self.logObject, "attempting to log a nil error for event: return;
}
os_log_error(self.logObject, "
NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
[eventAttributes setValuesForKeysWithDictionary:@{
SFSignInAnalyticsAttributeRecoverableError : @(YES),
SFSignInAnalyticsAttributeErrorDomain : error.domain,
SFSignInAnalyticsAttributeErrorCode : @(error.code),
SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
SFSignInAnalyticsAttributeEventName : self.eventName,
SFSignInAnalyticsAttributeSubsystemName : self.category
}];
[[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:eventAttributes];
}
- (void)logUnrecoverableError:(NSError*)error
{
if (error == nil){
os_log_error(self.logObject, "attempting to log a nil error for event: return;
}
os_log_error(self.logObject, "
NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
[eventAttributes setValuesForKeysWithDictionary:@{
SFSignInAnalyticsAttributeRecoverableError : @(NO),
SFSignInAnalyticsAttributeErrorDomain : error.domain,
SFSignInAnalyticsAttributeErrorCode : @(error.code),
SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
SFSignInAnalyticsAttributeEventName : self.eventName,
SFSignInAnalyticsAttributeSubsystemName : self.category
}];
[[SFSIALoggerObject logger] logHardFailureForEventNamed:self.eventName withAttributes:eventAttributes];
}
-(void)cancel
{
dispatch_sync(self.queue, ^{
[self.tracker cancel];
self.canceled = YES;
os_log(self.logObject, "canceled timer for });
}
- (void)stopWithAttributes:(NSDictionary<NSString*, id>*)attributes
{
dispatch_sync(self.queue, ^{
if(self.canceled || self.stopped){
return;
}
self.stopped = YES;
[self.tracker stop];
NSMutableDictionary *mutableAttributes = nil;
if(attributes){
mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
}
else{
mutableAttributes = [NSMutableDictionary dictionary];
}
mutableAttributes[SFSignInAnalyticsAttributeMyUUID] = self.my_uuid;
mutableAttributes[SFSignInAnalyticsAttributeParentUUID] = self.parent_uuid;
mutableAttributes[SFSignInAnalyticsAttributeSignInUUID] = self.signin_uuid;
mutableAttributes[SFSignInAnalyticsAttributeEventName] = self.eventName;
mutableAttributes[SFSignInAnalyticsAttributeSubsystemName] = self.category;
[mutableAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL * stop) {
os_log(self.logObject, "event: }];
[[SFSIALoggerObject logger] logSuccessForEventNamed:self.eventName];
[[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:mutableAttributes];
});
}
-(BOOL) writeResultsToTmp {
bool shouldWriteResultsToTemp = NO;
CFBooleanRef toTmp = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("DumpResultsToTemp"),
CFSTR("com.apple.security"),
kCFPreferencesAnyUser, kCFPreferencesAnyHost);
if(toTmp && CFGetTypeID(toTmp) == CFBooleanGetTypeID()){
if(toTmp == kCFBooleanFalse){
os_log(self.logObject, "writing results to splunk");
shouldWriteResultsToTemp = NO;
}
if(toTmp == kCFBooleanTrue){
os_log(self.logObject, "writing results to /tmp");
shouldWriteResultsToTemp = YES;
}
}
CFReleaseNull(toTmp);
return shouldWriteResultsToTemp;
}
- (void)processEventChainForUUID:(NSString*)uuid dependencyChain:(NSString*)dependencyChain
{
NSString* newChain = dependencyChain;
NSArray* children = [self.root.eventDependencyList objectForKey:uuid];
for (NSString* child in children) {
newChain = [NSString stringWithFormat:@" [self processEventChainForUUID:child dependencyChain:newChain];
}
if([children count] == 0){
[self.root.builtDependencyChains addObject:newChain];
os_log(self.logObject, "current dependency chain list: }
}
- (void)signInCompleted
{
//print final
os_log(self.logObject, "sign in complete");
NSError* error = nil;
//create dependency chains and log them
[self processEventChainForUUID:self.root.my_uuid dependencyChain:self.root.signin_uuid];
//write to database
if([self.root.builtDependencyChains count] > 0){
NSDictionary* eventAttributes = @{SFSignInAnalyticsAttributeBuiltDependencyChains : self.root.builtDependencyChains};
[[SFSIALoggerObject logger] logSoftFailureForEventNamed:SFSignInAnalyticsAttributeBuiltDependencyChains withAttributes:eventAttributes];
}
BOOL writingToTmp = [self writeResultsToTmp];
if(writingToTmp){ //writing sign in analytics to /tmp
os_log(self.logObject, "logging to /tmp");
NSData* eventData = [NSKeyedArchiver archivedDataWithRootObject:[[SFSIALoggerObject logger].database allEvents] requiringSecureCoding:YES error:&error];
if(eventData){
[eventData writeToFile:SFSignInAnalyticsDumpLoggedResultsToLocation options:0 error:&error];
if(error){
os_log_error(self.logObject, "error writing to file [ }else{
os_log(self.logObject, "successfully wrote sign in analytics to: }
}else{
os_log_error(self.logObject, "collected no data");
}
}else{ //writing to splunk
os_log(self.logObject, "logging to splunk");
}
//remove dependency list
BOOL removedPersistedDependencyList = [[NSFileManager defaultManager] removeItemAtPath:self.persistencePath error:&error];
if(!removedPersistedDependencyList || error){
os_log(self.logObject, "encountered error when attempting to remove persisted event list: }
}
@end
#endif