2 * Copyright (c) 2019 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "SecTapToRadar.h"
25 #import "utilities/debugging.h"
29 #import <MobileCoreServices/LSApplicationWorkspace.h>
30 #import <CoreFoundation/CFUserNotification.h>
31 #import <SoftLinking/SoftLinking.h>
33 #pragma clang diagnostic push
34 #pragma clang diagnostic ignored "-Wunused-function"
35 SOFT_LINK_FRAMEWORK(Frameworks, MobileCoreServices)
36 SOFT_LINK_CLASS(MobileCoreServices, LSApplicationWorkspace);
37 #pragma clang diagnostic pop
41 #include "utilities/SecInternalReleasePriv.h"
43 static NSString* kSecNextTTRDate = @"NextTTRDate";
44 static NSString* kSecPreferenceDomain = @"com.apple.security";
46 @interface SecTapToRadar ()
47 @property (readwrite) NSString *alert;
48 @property (readwrite) NSString *radarDescription;
49 @property (readwrite) NSString *radarnumber;
50 @property (readwrite) dispatch_queue_t queue;
54 static BOOL SecTTRDisabled = true;
56 @implementation SecTapToRadar
58 - (instancetype)initTapToRadar:(NSString *)alert
59 description:(NSString *)radarDescription
60 radar:(NSString *)radarnumber
62 if ((self = [super init]) == nil) {
67 _radarDescription = radarDescription;
68 _radarnumber = radarnumber;
69 _queue = dispatch_queue_create("com.apple.security.diagnostic-queue", 0);
70 dispatch_set_target_queue(_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
72 _componentName = @"Security";
73 _componentVersion = @"all";
74 _componentID = @"606179";
79 + (NSString *)keyname:(SecTapToRadar *)ttrRequest
81 return [NSString stringWithFormat:@"%@-%@", kSecNextTTRDate, ttrRequest.radarnumber];
84 + (BOOL)isRateLimited:(SecTapToRadar *)ttrRequest
86 return SecTTRDisabled || [ttrRequest isRateLimited];
91 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:kSecPreferenceDomain];
93 NSString *key = [[self class] keyname: self];
94 NSDate *val = [defaults valueForKey:key];
95 if (![val isKindOfClass:[NSDate class]]) {
96 [defaults removeObjectForKey:key];
100 if ([val compare:[NSDate date]] == NSOrderedAscending) {
107 + (void)disableTTRsEntirely {
108 SecTTRDisabled = YES;
112 - (void)updateRetryTimestamp
114 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:kSecPreferenceDomain];
115 [defaults setObject:[[NSDate date] dateByAddingTimeInterval:24*3600.0]
116 forKey:[[self class] keyname: self]];
119 - (void)clearRetryTimestamp
121 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:kSecPreferenceDomain];
122 [defaults removeObjectForKey:[[self class] keyname: self]];
126 + (void)triggerTapToRadar:(SecTapToRadar *)ttrRequest
128 secnotice("secttr", "Triggering TTR: %@", ttrRequest.alert);
129 dispatch_assert_queue(ttrRequest.queue);
132 static dispatch_once_t onceToken;
133 static NSMutableCharacterSet *queryComponent;
134 dispatch_once(&onceToken, ^{
135 queryComponent = [[NSMutableCharacterSet alloc] init];
136 [queryComponent formUnionWithCharacterSet:[NSCharacterSet URLQueryAllowedCharacterSet]];
137 [queryComponent removeCharactersInString:@"&"];
140 Class workspaceCls = getLSApplicationWorkspaceClass();
141 if (workspaceCls == NULL) {
145 NSString *title = [NSString stringWithFormat:@"Triggered SecTTR: %@ - %@", ttrRequest.alert, ttrRequest.radarnumber];
146 NSString *encodedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:queryComponent];
148 NSString *desc = [NSString stringWithFormat:@"%@\nRelated radar: radr://%@", ttrRequest.radarDescription, ttrRequest.radarnumber];
149 NSString *encodedDesc = [desc stringByAddingPercentEncodingWithAllowedCharacters:queryComponent];
151 NSString *url = [NSString stringWithFormat:@"tap-to-radar://new?"
152 "Reproducibilty=Always&"
155 "ComponentVersion=%@&"
156 "Reproducibility=Not%%20Applicable&"
158 "Classification=Crash/Hang/Data%%20Loss&"
161 [ttrRequest.componentName stringByAddingPercentEncodingWithAllowedCharacters:queryComponent],
162 [ttrRequest.componentVersion stringByAddingPercentEncodingWithAllowedCharacters:queryComponent],
163 [ttrRequest.componentID stringByAddingPercentEncodingWithAllowedCharacters:queryComponent],
166 NSURL *tapToRadarURL = [NSURL URLWithString:url];
167 [[workspaceCls defaultWorkspace] openURL:tapToRadarURL configuration:nil completionHandler:^(NSDictionary<NSString *,id> * __unused result, NSError * __unused error) {
172 + (BOOL)askUserIfTTR:(SecTapToRadar *)ttrRequest
177 NSDictionary *alertOptions = @{
178 (NSString *)kCFUserNotificationDefaultButtonTitleKey : @"Tap-To-Radar",
179 (NSString *)kCFUserNotificationAlternateButtonTitleKey : @"Go away",
180 (NSString *)kCFUserNotificationAlertMessageKey : ttrRequest.alert,
181 (NSString *)kCFUserNotificationAlertHeaderKey : @"Security",
185 CFUserNotificationRef notification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationPlainAlertLevel, &error, (__bridge CFDictionaryRef)alertOptions);
186 if (notification != NULL) {
187 CFOptionFlags responseFlags = 0;
188 CFUserNotificationReceiveResponse(notification, 1.0 * 60 * 3, &responseFlags);
189 switch (responseFlags & 0x03) {
190 case kCFUserNotificationDefaultResponse:
196 CFRelease(notification);
198 secnotice("SecTTR", "Failed to create notification error %@", @(error));
206 dispatch_sync(self.queue, ^{
208 if (!SecIsInternalRelease()) {
212 if ([[self class] isRateLimited:self]) {
213 secnotice("SecTTR", "Not showing ttr due to ratelimiting: %@", self.alert);
217 if ([[self class] askUserIfTTR:self]) {
218 [[self class] triggerTapToRadar:self];
220 [self updateRetryTimestamp];