Skip to content

Commit 0d53821

Browse files
committed
Add logic for minorVersionRecalculationThreshold
1 parent 930681f commit 0d53821

File tree

6 files changed

+77
-3
lines changed

6 files changed

+77
-3
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [2.0.5] - 2024-07-24
88
Requires macOS 12.0 and higher.
99

10+
### Added
11+
- To artificially change the `requredInstallationDate` to honor a previous macOS minor version, set `minorVersionRecalculationThreshold` under `osVersionRequirement` in amount of minor versions.
12+
- Ex: `minorVersionRecalculationThreshold` is set to 1 and SOFA feed has macOS 14.5 available
13+
- macOS device is 14.3: Target macOS 14.4.1 requiredInstallationDate of 2024-04-15 00:00:00 +0000
14+
- macOS device is 14.4: Target macOS 14.4.1 requiredInstallationDate of 2024-04-15 00:00:00 +0000
15+
- macOS device is 14.4.1: Target macOS 14.5 requiredInstallationDate of 2024-06-03 00:00:00 +0000
16+
- Addresses [612](https://github.com/macadmins/nudge/issues/612)
17+
1018
### Changed
1119
- The `Actively Exploited` logic internally within Nudge and the UI on the left sidebar will show `True` if any previous updates missing on the device had active exploits.
1220
- **WARNNG BREAKING CHANGE** - This changes the SLA computation and will result in a different `requiredInstallationDate` than offered in Nudge v2.0 -> v2.01.

Nudge/Preferences/DefaultPreferencesNudge.swift

+6
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ struct OSVersionRequirementVariables {
233233
""
234234
}
235235

236+
static var minorVersionRecalculationThreshold: Int {
237+
osVersionRequirementsProfile?.minorVersionRecalculationThreshold ??
238+
osVersionRequirementsJSON?.minorVersionRecalculationThreshold ??
239+
0
240+
}
241+
236242
static var nonActivelyExploitedCVEsMajorUpgradeSLA: Int {
237243
osVersionRequirementsProfile?.nonActivelyExploitedCVEsMajorUpgradeSLA ??
238244
osVersionRequirementsJSON?.nonActivelyExploitedCVEsMajorUpgradeSLA ??

Nudge/Preferences/PreferencesStructure.swift

+4
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ struct OSVersionRequirement: Codable {
151151
var activelyExploitedCVEsMajorUpgradeSLA: Int?
152152
var activelyExploitedCVEsMinorUpdateSLA: Int?
153153
var majorUpgradeAppPath: String?
154+
var minorVersionRecalculationThreshold: Int?
154155
var nonActivelyExploitedCVEsMajorUpgradeSLA: Int?
155156
var nonActivelyExploitedCVEsMinorUpdateSLA: Int?
156157
var requiredInstallationDate: Date?
@@ -170,6 +171,7 @@ extension OSVersionRequirement {
170171
self.activelyExploitedCVEsMajorUpgradeSLA = fromDictionary["activelyExploitedCVEsMajorUpgradeSLA"] as? Int
171172
self.activelyExploitedCVEsMinorUpdateSLA = fromDictionary["activelyExploitedCVEsMinorUpdateSLA"] as? Int
172173
self.majorUpgradeAppPath = fromDictionary["majorUpgradeAppPath"] as? String
174+
self.minorVersionRecalculationThreshold = fromDictionary["minorVersionRecalculationThreshold"] as? Int
173175
self.nonActivelyExploitedCVEsMajorUpgradeSLA = fromDictionary["nonActivelyExploitedCVEsMajorUpgradeSLA"] as? Int
174176
self.nonActivelyExploitedCVEsMinorUpdateSLA = fromDictionary["nonActivelyExploitedCVEsMinorUpdateSLA"] as? Int
175177
self.requiredMinimumOSVersion = fromDictionary["requiredMinimumOSVersion"] as? String
@@ -248,6 +250,7 @@ extension OSVersionRequirement {
248250
activelyExploitedCVEsMajorUpgradeSLA: Int? = nil,
249251
activelyExploitedCVEsMinorUpdateSLA: Int? = nil,
250252
majorUpgradeAppPath: String? = nil,
253+
minorVersionRecalculationThreshold: Int? = nil,
251254
nonActivelyExploitedCVEsMajorUpgradeSLA: Int? = nil,
252255
nonActivelyExploitedCVEsMinorUpdateSLA: Int? = nil,
253256
requiredInstallationDate: Date? = nil,
@@ -265,6 +268,7 @@ extension OSVersionRequirement {
265268
activelyExploitedCVEsMajorUpgradeSLA: activelyExploitedCVEsMajorUpgradeSLA ?? self.activelyExploitedCVEsMajorUpgradeSLA,
266269
activelyExploitedCVEsMinorUpdateSLA: activelyExploitedCVEsMinorUpdateSLA ?? self.activelyExploitedCVEsMinorUpdateSLA,
267270
majorUpgradeAppPath: majorUpgradeAppPath ?? self.majorUpgradeAppPath,
271+
minorVersionRecalculationThreshold: minorVersionRecalculationThreshold ?? self.minorVersionRecalculationThreshold,
268272
nonActivelyExploitedCVEsMajorUpgradeSLA: nonActivelyExploitedCVEsMajorUpgradeSLA ?? self.nonActivelyExploitedCVEsMajorUpgradeSLA,
269273
nonActivelyExploitedCVEsMinorUpdateSLA: nonActivelyExploitedCVEsMinorUpdateSLA ?? self.nonActivelyExploitedCVEsMinorUpdateSLA,
270274
requiredInstallationDate: requiredInstallationDate ?? self.requiredInstallationDate,

Nudge/UI/Main.swift

+30-3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
180180
if let macOSSOFAAssets = Globals.sofaAssets?.osVersions {
181181
// Get current installed OS version
182182
let currentInstalledVersion = GlobalVariables.currentOSVersion
183+
let currentMajorVersion = VersionManager.getMajorVersion(from: currentInstalledVersion)
183184

184185
for osVersion in macOSSOFAAssets {
185186
if PrefsWrapper.requiredMinimumOSVersion == "latest" {
@@ -224,10 +225,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
224225
allVersions.sort { VersionManager.versionLessThan(currentVersion: $0, newVersion: $1) }
225226

226227
// Filter versions between current and selected OS version
227-
let filteredVersions = allVersions.filter {
228+
let filteredVersions = VersionManager().removeDuplicates(from: allVersions.filter {
228229
VersionManager.versionGreaterThan(currentVersion: $0, newVersion: currentInstalledVersion) &&
229230
VersionManager.versionLessThanOrEqual(currentVersion: $0, newVersion: selectedOSVersion)
230-
}
231+
})
232+
233+
// Filter versions with the same major version as the current installed version
234+
let minorVersions = VersionManager().removeDuplicates(from: filteredVersions.filter { version in
235+
VersionManager.getMajorVersion(from: version) == currentMajorVersion
236+
})
231237

232238
// Count actively exploited CVEs in the filtered versions
233239
for osVersion in macOSSOFAAssets {
@@ -272,7 +278,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
272278
nudgePrimaryState.activelyExploitedCVEs = activelyExploitedCVEs
273279
releaseDate = selectedOS!.releaseDate ?? Date()
274280
if requiredInstallationDate == Date(timeIntervalSince1970: 0) {
275-
requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400))
281+
if OSVersionRequirementVariables.minorVersionRecalculationThreshold > 0 {
282+
let safeIndex = max(0, minorVersions.count - (OSVersionRequirementVariables.minorVersionRecalculationThreshold + 1)) // Ensure the index is within bounds
283+
let targetVersion = minorVersions[safeIndex]
284+
var foundVersion = false
285+
LogManager.notice("minorVersionRecalculationThreshold is set - Targeting macOS \(targetVersion) requiredInstallationDate via SOFA", logger: sofaLog)
286+
for osVersion in macOSSOFAAssets {
287+
for securityRelease in osVersion.securityReleases {
288+
if securityRelease.productVersion == targetVersion && VersionManager.versionLessThan(currentVersion: currentInstalledVersion, newVersion: targetVersion) {
289+
requiredInstallationDate = securityRelease.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400))
290+
foundVersion = true
291+
break
292+
}
293+
}
294+
}
295+
if !foundVersion {
296+
requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400))
297+
LogManager.warning("Could not find requiredInstallationDate from target macOS \(targetVersion)", logger: sofaLog)
298+
LogManager.notice("Setting requiredInstallationDate via SOFA to \(requiredInstallationDate)", logger: sofaLog)
299+
}
300+
} else {
301+
requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400))
302+
}
276303
LogManager.notice("Setting requiredInstallationDate via SOFA to \(requiredInstallationDate)", logger: sofaLog)
277304
}
278305
LogManager.notice("SOFA Matched OS Version: \(selectedOS!.productVersion)", logger: sofaLog)

Nudge/Utilities/Utils.swift

+10
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,10 @@ struct VersionManager {
14921492
return majorVersion
14931493
}
14941494

1495+
static func getMajorVersion(from version: String) -> Int {
1496+
return Int(version.split(separator: ".").first.map(String.init)!)!
1497+
}
1498+
14951499
static func getMinorOSVersion() -> Int {
14961500
var minorOSVersion = ProcessInfo().operatingSystemVersion.minorVersion
14971501
// if (CommandLineUtilities().simulateOSVersion() != nil) {
@@ -1516,6 +1520,12 @@ struct VersionManager {
15161520
versionGreaterThan(currentVersion: nudgePrimaryState.requiredMinimumOSVersion, newVersion: nudgePrimaryState.userRequiredMinimumOSVersion)
15171521
}
15181522

1523+
// Helper function to remove duplicates while preserving order
1524+
func removeDuplicates(from array: [String]) -> [String] {
1525+
var seen = Set<String>()
1526+
return array.filter { seen.insert($0).inserted }
1527+
}
1528+
15191529
// Adapted from https://stackoverflow.com/a/25453654
15201530
static func versionEqual(currentVersion: String, newVersion: String) -> Bool {
15211531
return currentVersion.compare(newVersion, options: .numeric) == .orderedSame

Schema/jamf/com.github.macadmins.Nudge.json

+19
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,25 @@
508508
}
509509
]
510510
},
511+
"minorVersionRecalculationThreshold": {
512+
"description": "The amount of minor versions a device can be behind before the requiredInstallationDate is recalculated against a previous update. (Note: This key is only used with Nudge v2.0.5 and higher)",
513+
"anyOf": [
514+
{
515+
"title": "Not Configured",
516+
"type": "null"
517+
},
518+
{
519+
"title": "Configured",
520+
"default": 0,
521+
"type": "integer",
522+
"options": {
523+
"inputAttributes": {
524+
"placeholder": "0"
525+
}
526+
}
527+
}
528+
]
529+
},
511530
"nonActivelyExploitedCVEsMajorUpgradeSLA": {
512531
"description": "When a major upgrade is not under active exploit but contains CVEs, this is the amount of days a user has to install the update. (Note: This key is only used with Nudge v2.0 and higher)",
513532
"anyOf": [

0 commit comments

Comments
 (0)