Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gui: Add config to disable notification silences #228

Merged
merged 3 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ objc_library(
module_name = "santa_common_SNTConfigState",
deps = [
":CoderMacros",
":SNTConfigurator",
":SNTCommonEnums",
":SNTConfigurator",
],
)

Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTConfigState.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// Properties here mirror the SNTConfigurator at a point in time
//
@property(readonly) SNTClientMode clientMode;
@property(readonly) BOOL enableNotificationSilences;

- (instancetype)initWithConfig:(SNTConfigurator *)config;

Expand Down
3 changes: 3 additions & 0 deletions Source/common/SNTConfigState.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ - (instancetype)initWithConfig:(SNTConfigurator *)config {
self = [super init];
if (self) {
_clientMode = config.clientMode;
_enableNotificationSilences = config.enableNotificationSilences;
}
return self;
}
Expand All @@ -33,12 +34,14 @@ + (BOOL)supportsSecureCoding {

- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE_BOXABLE(coder, clientMode);
ENCODE_BOXABLE(coder, enableNotificationSilences);
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
DECODE_SELECTOR(decoder, clientMode, NSNumber, integerValue);
DECODE_SELECTOR(decoder, enableNotificationSilences, NSNumber, boolValue);
}
return self;
};
Expand Down
9 changes: 9 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,15 @@
///
@property(nullable, readonly, nonatomic) NSString *modeNotificationStandalone;

///
/// If set to true, when a user is presented with a GUI notification there will be
/// a checkbox and dropdown to allow silencing these notifications for a short
/// period of time.
///
/// Defaults to true.
///
@property(readonly, nonatomic) BOOL enableNotificationSilences;

///
/// If this is set to true, the UI will use different fonts on April 1st, May 4th and October 31st.
///
Expand Down
11 changes: 11 additions & 0 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ @implementation SNTConfigurator
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kModeNotificationStandalone = @"ModeNotificationStandalone";
static NSString *const kEnableNotificationSilences = @"EnableNotificationSilences";
static NSString *const kFunFontsOnSpecificDays = @"FunFontsOnSpecificDays";

static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
Expand Down Expand Up @@ -243,6 +244,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kModeNotificationStandalone : string,
kEnableNotificationSilences : number,
kFunFontsOnSpecificDays : number,
kStaticRules : array,
kSyncBaseURLKey : string,
Expand Down Expand Up @@ -469,6 +471,10 @@ + (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
return [self configStateSet];
}

+ (NSSet *)keyPathsForValuesAffectingEnableNotificationSilences {
return [self configStateSet];
}

+ (NSSet *)keyPathsForValuesAffectingFunFontsOnSpecificDays {
return [self configStateSet];
}
Expand Down Expand Up @@ -870,6 +876,11 @@ - (NSString *)modeNotificationStandalone {
return self.configState[kModeNotificationStandalone];
}

- (BOOL)enableNotificationSilences {
NSNumber *number = self.configState[kEnableNotificationSilences];
return number ? [number boolValue] : YES;
}

- (BOOL)funFontsOnSpecificDays {
return [self.configState[kFunFontsOnSpecificDays] boolValue];
}
Expand Down
3 changes: 2 additions & 1 deletion Source/common/SNTXPCNotifierInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
customMessage:(NSString *)message
customURL:(NSString *)url
customText:(NSString *)text API_AVAILABLE(macos(13.0));
customText:(NSString *)text
configState:(SNTConfigState *)configState API_AVAILABLE(macos(13.0));
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
- (void)updateCountsForEvent:(SNTStoredEvent *)event
Expand Down
3 changes: 2 additions & 1 deletion Source/gui/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ swift_library(
deps = [
":SNTMessageView",
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigState",
"//Source/common:SNTFileAccessEvent",
],
)
Expand Down Expand Up @@ -101,9 +102,9 @@ objc_library(
":SNTFileAccessMessageWindowView",
"//Source/common:CertificateHelpers",
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigState",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTConfigState",
"//Source/common:SNTFileAccessEvent",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
Expand Down
4 changes: 3 additions & 1 deletion Source/gui/SNTBinaryMessageWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ struct SNTBinaryMessageWindowView: View {
) {
SNTBinaryMessageEventView(e: event!, customURL: customURL, bundleProgress: bundleProgress)

SNTNotificationSilenceView(silence: $preventFutureNotifications, period: $preventFutureNotificationPeriod)
if configState.enableNotificationSilences {
SNTNotificationSilenceView(silence: $preventFutureNotifications, period: $preventFutureNotificationPeriod)
}

if event?.needsBundleHash ?? false && !bundleProgress.isFinished {
if bundleProgress.fractionCompleted == 0.0 {
Expand Down
4 changes: 3 additions & 1 deletion Source/gui/SNTFileAccessMessageWindowController.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

NS_ASSUME_NONNULL_BEGIN

@class SNTConfigState;
@class SNTFileAccessEvent;

///
Expand All @@ -29,7 +30,8 @@ API_AVAILABLE(macos(13.0))
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event
customMessage:(nullable NSString *)message
customURL:(nullable NSString *)url
customText:(nullable NSString *)text;
customText:(nullable NSString *)text
configState:(nullable SNTConfigState *)configState;

@property(readonly) SNTFileAccessEvent *event;

Expand Down
7 changes: 6 additions & 1 deletion Source/gui/SNTFileAccessMessageWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
#import "Source/gui/SNTFileAccessMessageWindowView-Swift.h"

#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigState.h"
#import "Source/common/SNTFileAccessEvent.h"
#import "Source/common/SNTLogging.h"

@interface SNTFileAccessMessageWindowController ()
@property NSString *customMessage;
@property NSString *customURL;
@property NSString *customText;
@property SNTConfigState *configState;
@property SNTFileAccessEvent *event;
@end

Expand All @@ -32,13 +34,15 @@ @implementation SNTFileAccessMessageWindowController
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event
customMessage:(nullable NSString *)message
customURL:(nullable NSString *)url
customText:(nullable NSString *)text {
customText:(nullable NSString *)text
configState:(nullable SNTConfigState *)configState {
self = [super init];
if (self) {
_event = event;
_customMessage = message;
_customURL = url;
_customText = text;
_configState = configState;
}
return self;
}
Expand Down Expand Up @@ -70,6 +74,7 @@ - (void)showWindow:(id)sender {
customMessage:self.customMessage
customURL:self.customURL
customText:self.customText
configState:self.configState
uiStateCallback:^(NSTimeInterval preventNotificationsPeriod) {
self.silenceFutureNotificationsPeriod = preventNotificationsPeriod;
}];
Expand Down
11 changes: 7 additions & 4 deletions Source/gui/SNTFileAccessMessageWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import SwiftUI

import santa_common_SNTBlockMessage
import santa_common_SNTConfigState
import santa_common_SNTFileAccessEvent
import santa_gui_SNTMessageView

Expand All @@ -27,6 +28,7 @@ import santa_gui_SNTMessageView
customMessage: NSString?,
customURL: NSString?,
customText: NSString?,
configState: SNTConfigState,
uiStateCallback: ((TimeInterval) -> Void)?
) -> NSViewController {
return NSHostingController(
Expand All @@ -36,6 +38,7 @@ import santa_gui_SNTMessageView
customMessage: customMessage,
customURL: customURL as String?,
customText: customText as String?,
configState: configState,
uiStateCallback: uiStateCallback
)
.frame(minWidth: MAX_OUTER_VIEW_WIDTH, minHeight: MAX_OUTER_VIEW_HEIGHT)
Expand Down Expand Up @@ -168,6 +171,7 @@ struct SNTFileAccessMessageWindowView: View {
let customMessage: NSString?
let customURL: String?
let customText: String?
let configState: SNTConfigState
let uiStateCallback: ((TimeInterval) -> Void)?

@Environment(\.openURL) var openURL
Expand All @@ -182,10 +186,9 @@ struct SNTFileAccessMessageWindowView: View {
Event(e: event!, window: window)

VStack(spacing: 15.0) {
SNTNotificationSilenceView(
silence: $preventFutureNotifications,
period: $preventFutureNotificationPeriod
)
if configState.enableNotificationSilences {
SNTNotificationSilenceView(silence: $preventFutureNotifications, period: $preventFutureNotificationPeriod)
}

HStack(spacing: 15.0) {
if customURL != nil {
Expand Down
43 changes: 24 additions & 19 deletions Source/gui/SNTNotificationManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ - (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
return NO;
}

- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg
enableSilences:(BOOL)enableSilences {
// Post a distributed notification, regardless of queue state.
[self postDistributedNotification:pendingMsg];

Expand All @@ -130,19 +131,21 @@ - (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
}

// See if this message has been user-silenced.
NSString *messageHash = [pendingMsg messageHash];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
switch ([silenceDate compare:[NSDate date]]) {
case NSOrderedDescending:
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
case NSOrderedAscending:
LOGI(@"Notification silence: silence has expired, deleting");
[self updateSilenceDate:nil forHash:messageHash];
break;
default: break;
if (enableSilences) {
NSString *messageHash = [pendingMsg messageHash];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
switch ([silenceDate compare:[NSDate date]]) {
case NSOrderedDescending:
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
case NSOrderedAscending:
LOGI(@"Notification silence: silence has expired, deleting");
[self updateSilenceDate:nil forHash:messageHash];
break;
default: break;
}
}
}

Expand Down Expand Up @@ -378,7 +381,7 @@ - (void)postBlockNotification:(SNTStoredEvent *)event
configState:configState
reply:replyBlock];

[self queueMessage:pendingMsg];
[self queueMessage:pendingMsg enableSilences:configState.enableNotificationSilences];
}

- (void)postUSBBlockNotification:(SNTDeviceEvent *)event {
Expand All @@ -389,13 +392,14 @@ - (void)postUSBBlockNotification:(SNTDeviceEvent *)event {
SNTDeviceMessageWindowController *pendingMsg =
[[SNTDeviceMessageWindowController alloc] initWithEvent:event];

[self queueMessage:pendingMsg];
[self queueMessage:pendingMsg enableSilences:YES];
}

- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
customMessage:(NSString *)message
customURL:(NSString *)url
customText:(NSString *)text API_AVAILABLE(macos(13.0)) {
customText:(NSString *)text
configState:(SNTConfigState *)configState API_AVAILABLE(macos(13.0)) {
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
Expand All @@ -405,9 +409,10 @@ - (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
[[SNTFileAccessMessageWindowController alloc] initWithEvent:event
customMessage:message
customURL:url
customText:text];
customText:text
configState:configState];

[self queueMessage:pendingMsg];
[self queueMessage:pendingMsg enableSilences:YES];
}

// XPC handler. The sync service requests the APNS token, by way of the daemon.
Expand Down
21 changes: 15 additions & 6 deletions Source/gui/SNTTestGUI.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI

import santa_common_SNTConfigState
import santa_common_SNTConfigurator
import santa_common_SNTCommonEnums
import santa_common_SNTDeviceEvent
Expand Down Expand Up @@ -58,10 +59,11 @@ struct BinaryView: View {
@State var path: String = "/Applications/Malware.app/Contents/MacOS"
@State var parent: String = "launchd"

@State var bannedBlockMessage: String = ""
@State var unknownBlockMessage: String = ""
@State var eventDetailURL: String = "http://sync-server-hostname/blockables/%bundle_or_file_identifier%"
@State var dateOverride: SpecialDates = .Nov25
@State var clientModeOverride: SNTClientMode = .lockdown
@State var allowNotificationSilence: Bool = true

@State var customMsg: String = ""
@State var customURL: String = ""
Expand All @@ -83,17 +85,17 @@ struct BinaryView: View {
GroupBox(label: Label("Config Overrides", systemImage: "")) {
Form {
HStack {
TextField(text: $bannedBlockMessage, label: { Text(verbatim: "Banned Block Message") }).frame(width: 550.0)
TextField(text: $unknownBlockMessage, label: { Text(verbatim: "Banned Block Message") }).frame(width: 550.0)
Button(action: {
bannedBlockMessage =
unknownBlockMessage =
"<img src='https://static.wikia.nocookie.net/villains/images/8/8a/Robot_Santa.png/revision/latest?cb=20200520230856' /><br /><br />Isn't Santa fun?"
}) {
Text(verbatim: "Populate (With Image)").font(Font.subheadline)
}
Button(action: { bannedBlockMessage = "You may not run this thing" }) {
Button(action: { unknownBlockMessage = "You may not run this thing" }) {
Text(verbatim: "Populate (1-line)").font(Font.subheadline)
}
Button(action: { bannedBlockMessage = "" }) { Text("Clear").font(Font.subheadline) }
Button(action: { unknownBlockMessage = "" }) { Text("Clear").font(Font.subheadline) }
}

HStack {
Expand All @@ -118,17 +120,23 @@ struct BinaryView: View {
Text(verbatim: "Standalone").tag(SNTClientMode.standalone)
}.pickerStyle(.segmented)
}
HStack {
Toggle(isOn: $allowNotificationSilence) {
Text(verbatim: "Allow notification silences")
}
}
}
}

Divider()

Button("Display") {
var configMap = [
"BannedBlockMessage": bannedBlockMessage,
"FunFontsOnSpecificDays": true,
"ClientMode": clientModeOverride.rawValue as NSNumber,
"EnableStandalonePasswordFallback": true,
"UnknownBlockMessage": unknownBlockMessage,
"EnableNotificationSilences": allowNotificationSilence,
]
if !eventDetailURL.isEmpty {
configMap["EventDetailURL"] = eventDetailURL
Expand Down Expand Up @@ -161,6 +169,7 @@ struct BinaryView: View {
event: event,
customMsg: customMsg as NSString?,
customURL: customURL as NSString?,
configState: SNTConfigState(config: SNTConfigurator.configurator()),
bundleProgress: SNTBundleProgress(),
uiStateCallback: { interval in print("Silence interval was set to \(interval)") },
replyCallback: { approved in print("Did user approve execution: \(approved)") }
Expand Down
Loading
Loading