• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / santa / 10309967616

08 Aug 2024 09:43PM UTC coverage: 62.69% (-0.4%) from 63.125%
10309967616

push

github

web-flow
sync: Fix Content-Type logic bug, add test (#1412)

5612 of 13404 branches covered (41.87%)

Branch coverage included in aggregate %.

12 of 12 new or added lines in 2 files covered. (100.0%)

170 existing lines in 8 files now uncovered.

18659 of 25312 relevant lines covered (73.72%)

6451.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.67
/Source/gui/SNTNotificationManager.m
1
/// Copyright 2015 Google Inc. All rights reserved.
2
///
3
/// Licensed under the Apache License, Version 2.0 (the "License");
4
/// you may not use this file except in compliance with the License.
5
/// You may obtain a copy of the License at
6
///
7
///    http://www.apache.org/licenses/LICENSE-2.0
8
///
9
///    Unless required by applicable law or agreed to in writing, software
10
///    distributed under the License is distributed on an "AS IS" BASIS,
11
///    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
///    See the License for the specific language governing permissions and
13
///    limitations under the License.
14

15
#import "Source/gui/SNTNotificationManager.h"
16

17
#import <MOLCertificate/MOLCertificate.h>
18
#import <MOLXPCConnection/MOLXPCConnection.h>
19
#import <UserNotifications/UserNotifications.h>
20

21
#import "Source/common/SNTBlockMessage.h"
22
#import "Source/common/SNTConfigurator.h"
23
#import "Source/common/SNTDeviceEvent.h"
24
#import "Source/common/SNTLogging.h"
25
#import "Source/common/SNTStoredEvent.h"
26
#import "Source/common/SNTStrengthify.h"
27
#import "Source/common/SNTSyncConstants.h"
28
#import "Source/common/SNTXPCControlInterface.h"
29
#import "Source/gui/SNTBinaryMessageWindowController.h"
30
#import "Source/gui/SNTDeviceMessageWindowController.h"
31
#import "Source/gui/SNTFileAccessMessageWindowController.h"
32
#import "Source/gui/SNTMessageWindowController.h"
33

34
@interface SNTNotificationManager ()
35

36
///  The currently displayed notification
37
@property SNTMessageWindowController *currentWindowController;
38

39
///  The queue of pending notifications
40
@property(readonly) NSMutableArray *pendingNotifications;
41

42
// A serial queue for holding hashBundleBinaries requests
43
@property dispatch_queue_t hashBundleBinariesQueue;
44

45
@end
46

47
@implementation SNTNotificationManager
48

49
static NSString *const silencedNotificationsKey = @"SilencedNotifications";
50

51
- (instancetype)init {
1✔
52
  self = [super init];
1✔
53
  if (self) {
1!
54
    _pendingNotifications = [[NSMutableArray alloc] init];
1✔
55
    _hashBundleBinariesQueue =
1✔
56
      dispatch_queue_create("com.google.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL);
1✔
57
  }
1✔
58
  return self;
1✔
59
}
1✔
60

61
- (void)windowDidCloseSilenceHash:(NSString *)hash {
×
62
  if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
×
63

64
  [self.pendingNotifications removeObject:self.currentWindowController];
×
65
  self.currentWindowController = nil;
×
66

67
  if (self.pendingNotifications.count) {
×
68
    [self showQueuedWindow];
×
69
  } else {
×
70
    MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
×
71
    [bc resume];
×
72
    [[bc remoteObjectProxy] spindown];
×
73
    [bc invalidate];
×
74
    // Remove app from Cmd+Tab and Dock.
75
    NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
×
76
    [NSApp hide:self];
×
77
  }
×
78
}
×
79

80
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
×
81
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
×
82
  NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
×
83
  if (!d) d = [NSMutableDictionary dictionary];
×
84
  if (date) {
×
85
    d[hash] = date;
×
86
  } else {
×
87
    [d removeObjectForKey:hash];
×
88
  }
×
89
  [ud setObject:d forKey:silencedNotificationsKey];
×
90
}
×
91

UNCOV
92
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
×
UNCOV
93
  for (SNTMessageWindowController *msg in self.pendingNotifications) {
×
94
    if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
×
95
  }
×
UNCOV
96
  return NO;
×
UNCOV
97
}
×
98

99
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
1✔
100
  // Post a distributed notification, regardless of queue state.
101
  [self postDistributedNotification:pendingMsg];
1✔
102

103
  // If GUI is in silent mode or if there's already a notification queued for
104
  // this message, don't do anything else.
105
  if ([SNTConfigurator configurator].enableSilentMode) return;
1!
106

107
  dispatch_async(dispatch_get_main_queue(), ^{
1✔
UNCOV
108
    if ([self notificationAlreadyQueued:pendingMsg]) return;
×
109

110
    // See if this message has been user-silenced.
UNCOV
111
    NSString *messageHash = [pendingMsg messageHash];
×
UNCOV
112
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
×
UNCOV
113
    NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
×
UNCOV
114
    if ([silenceDate isKindOfClass:[NSDate class]]) {
×
115
      NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
×
116
      if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
×
117
        LOGI(@"Notification silence: date is in the future, ignoring");
×
118
        [self updateSilenceDate:nil forHash:messageHash];
×
119
      } else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
×
120
        LOGI(@"Notification silence: date is more than one day ago, ignoring");
×
121
        [self updateSilenceDate:nil forHash:messageHash];
×
122
      } else {
×
123
        LOGI(@"Notification silence: dropping notification for %@", messageHash);
×
124
        return;
×
125
      }
×
126
    }
×
127

UNCOV
128
    pendingMsg.delegate = self;
×
UNCOV
129
    [self.pendingNotifications addObject:pendingMsg];
×
130

UNCOV
131
    if (!self.currentWindowController) {
×
132
      // Add app to Cmd+Tab and Dock.
UNCOV
133
      NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
×
UNCOV
134
      [self showQueuedWindow];
×
UNCOV
135
    }
×
UNCOV
136
  });
×
137
}
1✔
138

139
// For blocked execution notifications, post an NSDistributedNotificationCenter
140
// notification with the important details from the stored event. Distributed
141
// notifications are system-wide broadcasts that can be sent by apps and observed
142
// from separate processes. This allows users of Santa to write tools that
143
// perform actions when we block execution, such as trigger management tools or
144
// display an enterprise-specific UI (which is particularly useful when combined
145
// with the EnableSilentMode configuration option, to disable Santa's standard UI).
146
- (void)postDistributedNotification:(SNTMessageWindowController *)pendingMsg {
1✔
147
  if (![pendingMsg isKindOfClass:[SNTBinaryMessageWindowController class]]) {
1!
148
    return;
×
149
  }
×
150
  SNTBinaryMessageWindowController *wc = (SNTBinaryMessageWindowController *)pendingMsg;
1✔
151
  NSDistributedNotificationCenter *dc = [NSDistributedNotificationCenter defaultCenter];
1✔
152
  NSMutableArray<NSDictionary *> *signingChain =
1✔
153
    [NSMutableArray arrayWithCapacity:wc.event.signingChain.count];
1✔
154
  for (MOLCertificate *cert in wc.event.signingChain) {
1✔
155
    [signingChain addObject:@{
×
156
      kCertSHA256 : cert.SHA256 ?: @"",
×
157
      kCertCN : cert.commonName ?: @"",
×
158
      kCertOrg : cert.orgName ?: @"",
×
159
      kCertOU : cert.orgUnit ?: @"",
×
160
      kCertValidFrom : @([cert.validFrom timeIntervalSince1970]) ?: @0,
×
161
      kCertValidUntil : @([cert.validUntil timeIntervalSince1970]) ?: @0,
×
162
    }];
×
163
  }
×
164
  NSDictionary *userInfo = @{
1✔
165
    kFileSHA256 : wc.event.fileSHA256 ?: @"",
1!
166
    kFilePath : wc.event.filePath ?: @"",
4,294,967,296✔
167
    kFileBundleName : wc.event.fileBundleName ?: @"",
4,294,967,295✔
168
    kFileBundleID : wc.event.fileBundleID ?: @"",
4,294,967,294✔
169
    kFileBundleVersion : wc.event.fileBundleVersion ?: @"",
4,294,967,293✔
170
    kFileBundleShortVersionString : wc.event.fileBundleVersionString ?: @"",
4,294,967,292✔
171
    kTeamID : wc.event.teamID ?: @"",
4,294,967,291!
172
    kExecutingUser : wc.event.executingUser ?: @"",
4,294,967,291✔
173
    kExecutionTime : @([wc.event.occurrenceDate timeIntervalSince1970]) ?: @0,
4,294,967,290✔
174
    kPID : wc.event.pid ?: @0,
4,294,967,289✔
175
    kPPID : wc.event.ppid ?: @0,
4,294,967,288✔
176
    kParentName : wc.event.parentName ?: @"",
4,294,967,287✔
177
    kSigningChain : signingChain,
178
  };
179

180
  [dc postNotificationName:@"com.google.santa.notification.blockedeexecution"
181
                    object:@"com.google.santa"
182
                  userInfo:userInfo
183
        deliverImmediately:YES];
184
}
185

UNCOV
186
- (void)showQueuedWindow {
×
187
  // Notifications arrive on a background thread but UI updates must happen on the main thread.
188
  // This includes making windows.
UNCOV
189
  dispatch_async(dispatch_get_main_queue(), ^{
×
190
    // If a notification isn't currently being displayed, display the incoming one.
191
    // This check will generally be redundant, as we'd generally want to check this prior to
192
    // starting work on the main thread.
UNCOV
193
    if (!self.currentWindowController) {
×
UNCOV
194
      self.currentWindowController = [self.pendingNotifications firstObject];
×
UNCOV
195
      [self.currentWindowController showWindow:self];
×
196

UNCOV
197
      if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
×
UNCOV
198
        SNTBinaryMessageWindowController *controller =
×
UNCOV
199
          (SNTBinaryMessageWindowController *)self.currentWindowController;
×
UNCOV
200
        dispatch_async(self.hashBundleBinariesQueue, ^{
×
UNCOV
201
          [self hashBundleBinariesForEvent:controller.event withController:controller];
×
UNCOV
202
        });
×
UNCOV
203
      }
×
UNCOV
204
    }
×
UNCOV
205
  });
×
UNCOV
206
}
×
207

208
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
UNCOV
209
                    withController:(SNTBinaryMessageWindowController *)withController {
×
UNCOV
210
  withController.foundFileCountLabel.stringValue = @"Searching for files...";
×
211

UNCOV
212
  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
×
UNCOV
213
  MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
×
UNCOV
214
  bc.acceptedHandler = ^{
×
215
    dispatch_semaphore_signal(sema);
×
216
  };
×
UNCOV
217
  [bc resume];
×
218

219
  // Wait a max of 5 secs for the bundle service
220
  // Otherwise abandon bundle hashing and display the blockable event.
UNCOV
221
  if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
×
222
    [withController updateBlockNotification:event withBundleHash:nil];
×
223
    LOGE(@"Timeout connecting to bundle service");
×
224
    return;
×
225
  }
×
226

UNCOV
227
  [[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
×
228

229
  // NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
UNCOV
230
  [withController.progress becomeCurrentWithPendingUnitCount:100];
×
231

232
  // Start hashing. Progress is reported to the root NSProgress
233
  // (currentWindowController.progress).
UNCOV
234
  [[bc remoteObjectProxy]
×
UNCOV
235
    hashBundleBinariesForEvent:event
×
UNCOV
236
                         reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
×
237
                           // Revert to displaying the blockable event if we fail to calculate the
238
                           // bundle hash
239
                           if (!bh)
×
240
                             return [withController updateBlockNotification:event
×
241
                                                             withBundleHash:nil];
×
242

243
                           event.fileBundleHash = bh;
×
244
                           event.fileBundleBinaryCount = @(events.count);
×
245
                           event.fileBundleHashMilliseconds = ms;
×
246
                           event.fileBundleExecutableRelPath =
×
247
                             [events.firstObject fileBundleExecutableRelPath];
×
248
                           for (SNTStoredEvent *se in events) {
×
249
                             se.fileBundleHash = bh;
×
250
                             se.fileBundleBinaryCount = @(events.count);
×
251
                             se.fileBundleHashMilliseconds = ms;
×
252
                           }
×
253

254
                           // Send the results to santad. It will decide if they need to be
255
                           // synced.
256
                           MOLXPCConnection *daemonConn =
×
257
                             [SNTXPCControlInterface configuredConnection];
×
258
                           [daemonConn resume];
×
259
                           [[daemonConn remoteObjectProxy] syncBundleEvent:event
×
260
                                                             relatedEvents:events];
×
261
                           [daemonConn invalidate];
×
262

263
                           // Update the UI with the bundle hash. Also make the openEventButton
264
                           // available.
265
                           [withController updateBlockNotification:event withBundleHash:bh];
×
266

267
                           [bc invalidate];
×
268
                         }];
×
269

UNCOV
270
  [withController.progress resignCurrent];
×
UNCOV
271
}
×
272

273
#pragma mark SNTNotifierXPC protocol methods
274

275
- (void)postClientModeNotification:(SNTClientMode)clientmode {
×
276
  if ([SNTConfigurator configurator].enableSilentMode) return;
×
277

278
  UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
×
279

280
  UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
×
281
  content.title = @"Santa";
×
282

283
  switch (clientmode) {
×
284
    case SNTClientModeMonitor: {
×
285
      content.body = @"Switching into Monitor mode";
×
286
      NSString *customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
×
287
      if (!customMsg) break;
×
288
      // If a custom message is added but as an empty string, disable notifications.
289
      if (!customMsg.length) return;
×
290

291
      content.body = [SNTBlockMessage stringFromHTML:customMsg];
×
292
      break;
×
293
    }
×
294
    case SNTClientModeLockdown: {
×
295
      content.body = @"Switching into Lockdown mode";
×
296
      NSString *customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
×
297
      if (!customMsg) break;
×
298
      // If a custom message is added but as an empty string, disable notifications.
299
      if (!customMsg.length) return;
×
300

301
      content.body = [SNTBlockMessage stringFromHTML:customMsg];
×
302
      break;
×
303
    }
×
304
    default: return;
×
305
  }
×
306

307
  UNNotificationRequest *req =
×
308
    [UNNotificationRequest requestWithIdentifier:@"clientModeNotification"
×
309
                                         content:content
×
310
                                         trigger:nil];
×
311

312
  [un addNotificationRequest:req withCompletionHandler:nil];
×
313
}
×
314

315
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
×
316
  if ([SNTConfigurator configurator].enableSilentMode) return;
×
317

318
  UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
×
319

320
  UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
×
321
  content.title = @"Santa";
×
322
  content.body = message ?: @"Requested application can now be run";
×
323

324
  NSString *identifier = [NSString stringWithFormat:@"ruleSyncNotification_%@", content.body];
×
325

326
  UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:identifier
×
327
                                                                    content:content
×
328
                                                                    trigger:nil];
×
329

330
  [un addNotificationRequest:req withCompletionHandler:nil];
×
331
}
×
332

333
- (void)postBlockNotification:(SNTStoredEvent *)event
334
            withCustomMessage:(NSString *)message
335
                 andCustomURL:(NSString *)url {
1✔
336
  if (!event) {
1!
337
    LOGI(@"Error: Missing event object in message received from daemon!");
×
338
    return;
×
339
  }
×
340

341
  SNTBinaryMessageWindowController *pendingMsg =
1✔
342
    [[SNTBinaryMessageWindowController alloc] initWithEvent:event customMsg:message customURL:url];
1✔
343

344
  [self queueMessage:pendingMsg];
1✔
345
}
1✔
346

347
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
×
348
  if (!event) {
×
349
    LOGI(@"Error: Missing event object in message received from daemon!");
×
350
    return;
×
351
  }
×
352
  SNTDeviceMessageWindowController *pendingMsg =
×
353
    [[SNTDeviceMessageWindowController alloc] initWithEvent:event message:message];
×
354

355
  [self queueMessage:pendingMsg];
×
356
}
×
357

358
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
359
                          customMessage:(NSString *)message
360
                              customURL:(NSString *)url
361
                             customText:(NSString *)text API_AVAILABLE(macos(13.0)) {
×
362
  if (!event) {
×
363
    LOGI(@"Error: Missing event object in message received from daemon!");
×
364
    return;
×
365
  }
×
366

367
  SNTFileAccessMessageWindowController *pendingMsg =
×
368
    [[SNTFileAccessMessageWindowController alloc] initWithEvent:event
×
369
                                                  customMessage:message
×
370
                                                      customURL:url
×
371
                                                     customText:text];
×
372

373
  [self queueMessage:pendingMsg];
×
374
}
×
375

376
#pragma mark SNTBundleNotifierXPC protocol methods
377

378
- (void)updateCountsForEvent:(SNTStoredEvent *)event
379
                 binaryCount:(uint64_t)binaryCount
380
                   fileCount:(uint64_t)fileCount
381
                 hashedCount:(uint64_t)hashedCount {
×
382
  if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
×
383
    SNTBinaryMessageWindowController *controller =
×
384
      (SNTBinaryMessageWindowController *)self.currentWindowController;
×
385

386
    if ([controller.event.idx isEqual:event.idx]) {
×
387
      dispatch_async(dispatch_get_main_queue(), ^{
×
388
        controller.foundFileCountLabel.stringValue =
×
389
          [NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
×
390
                                     hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
×
391
      });
×
392
    }
×
393
  }
×
394
}
×
395

396
@end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc