diff --git a/.github/workflows/build_nudge_pr.yml b/.github/workflows/build_nudge_pr.yml index 1efe4b87..17ab5554 100644 --- a/.github/workflows/build_nudge_pr.yml +++ b/.github/workflows/build_nudge_pr.yml @@ -11,20 +11,20 @@ jobs: steps: - name: Checkout nudge repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Install Apple Xcode certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: keychain-password: ${{ github.run_id }} p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Install Apple Installer certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -41,7 +41,7 @@ jobs: echo "NUDGE_MAIN_VERSION=$(/bin/cat ./build_info_main.txt)" >> $GITHUB_ENV - name: Upload zip archive - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages path: outputs/ diff --git a/.github/workflows/build_nudge_prerelease.yml b/.github/workflows/build_nudge_prerelease.yml index 56983b78..6acf5dd6 100644 --- a/.github/workflows/build_nudge_prerelease.yml +++ b/.github/workflows/build_nudge_prerelease.yml @@ -19,19 +19,19 @@ jobs: steps: - name: Checkout nudge repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Install Apple Xcode certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: keychain-password: ${{ github.run_id }} p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Install Apple Installer certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -89,7 +89,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages path: outputs/ diff --git a/.github/workflows/build_nudge_prerelease_manual.yml b/.github/workflows/build_nudge_prerelease_manual.yml index 141937db..9bbc2092 100644 --- a/.github/workflows/build_nudge_prerelease_manual.yml +++ b/.github/workflows/build_nudge_prerelease_manual.yml @@ -11,19 +11,19 @@ jobs: steps: - name: Checkout nudge repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Install Apple Xcode certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: keychain-password: ${{ github.run_id }} p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Install Apple Installer certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -81,7 +81,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages path: outputs/ diff --git a/.github/workflows/build_nudge_release.yml b/.github/workflows/build_nudge_release.yml index d5ccbece..f84fe238 100644 --- a/.github/workflows/build_nudge_release.yml +++ b/.github/workflows/build_nudge_release.yml @@ -19,19 +19,19 @@ jobs: steps: - name: Checkout nudge repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Install Apple Xcode certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: keychain-password: ${{ github.run_id }} p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Install Apple Installer certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -89,7 +89,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages path: outputs/ diff --git a/.github/workflows/build_nudge_release_manual.yml b/.github/workflows/build_nudge_release_manual.yml index 97551cb5..a60f703d 100644 --- a/.github/workflows/build_nudge_release_manual.yml +++ b/.github/workflows/build_nudge_release_manual.yml @@ -11,19 +11,19 @@ jobs: steps: - name: Checkout nudge repo - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Install Apple Xcode certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: keychain-password: ${{ github.run_id }} p12-file-base64: ${{ secrets.APP_CERTIFICATES_P12_MAOS }} p12-password: ${{ secrets.APP_CERTIFICATES_P12_PASSWORD_MAOS }} - name: Install Apple Installer certificates - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 + uses: apple-actions/import-codesign-certs@63fff01cd422d4b7b855d40ca1e9d34d2de9427d # v3.0.0 with: create-keychain: false # do not create a new keychain for this value keychain-password: ${{ github.run_id }} @@ -81,7 +81,7 @@ jobs: files: ${{github.workspace}}/outputs/*.pkg - name: Upload packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages path: outputs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 843d3546..d4637f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,43 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.5] - 2024-07-24 +Requires macOS 12.0 and higher. + +### Added +- To artificially change the `requredInstallationDate` to honor a previous macOS minor version, set `minorVersionRecalculationThreshold` under `osVersionRequirement` in amount of minor versions. + - Ex: `minorVersionRecalculationThreshold` is set to 1 and SOFA feed has macOS 14.5 available + - macOS device is 14.0: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-08 00:00:00 +0000 + - macOS device is 14.1: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-08 00:00:00 +0000 + - macOS device is 14.2: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-08 00:00:00 +0000 + - macOS device is 14.3: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-08 00:00:00 +0000 + - macOS device is 14.4: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-08 00:00:00 +0000 + - macOS device is 14.4.1: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-15 00:00:00 +0000 + - This device's requiredInstallationDate is different than the others as there is no active exploit on 14.4.1 + - macOS device is 14.5: Required OS: 14.5 - Fully updated + - Ex: `minorVersionRecalculationThreshold` is set to 2 and SOFA feed has macOS 14.5 available + - macOS device is 14.0: Required OS: 14.5 - Target macOS 14.4 requiredInstallationDate of 2024-03-21 00:00:00 +0000 + - macOS device is 14.1: Required OS: 14.5 - Target macOS 14.4 requiredInstallationDate of 2024-03-21 00:00:00 +0000 + - macOS device is 14.2: Required OS: 14.5 - Target macOS 14.4 requiredInstallationDate of 2024-03-21 00:00:00 +0000 + - macOS device is 14.3: Required OS: 14.5 - Target macOS 14.4 requiredInstallationDate of 2024-03-21 00:00:00 +0000 + - macOS device is 14.4: Required OS: 14.5 - Target macOS 14.4 requiredInstallationDate of 2024-03-21 00:00:00 +0000 + - macOS device is 14.4.1: Required OS: 14.5 - Target macOS 14.4.1 requiredInstallationDate of 2024-04-15 00:00:00 +0000 + - macOS device is 14.5: Required OS: 14.5 - Fully updated + - Addresses [612](https://github.com/macadmins/nudge/issues/612) + +### Changed +- 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. + - **WARNNG BREAKING CHANGE** - This changes the SLA computation and will result in a different `requiredInstallationDate` than offered in Nudge v2.0 -> v2.01. + - Ex: Device is on 14.3 and needing to go to 14.5. + - While 14.4.1 -> 14.5 are not under active exploit, 14.4 contains fixes for 14.3 that were under active exploit. + - Addresses [610](https://github.com/macadmins/nudge/issues/610) and [613](https://github.com/macadmins/nudge/issues/613) +- When `showRequiredDate` is set to `True` and the admin is using the default values for `requiredInstallationDisplayFormat`, Nudge will attempt to understand the current locale and display the menu item appropriately. + - Addresses [615](https://github.com/macadmins/nudge/issues/615) + +### Fixed +- Several components in the Github Actions were triggering deprecation warnings. These have been addressed by updating to the latest version of these components + - Addresses [616](https://github.com/macadmins/nudge/issues/616) + ## [2.0.4] - 2024-07-23 Requires macOS 12.0 and higher. diff --git a/Nudge.xcodeproj/project.pbxproj b/Nudge.xcodeproj/project.pbxproj index 4ea9750c..88ffddff 100644 --- a/Nudge.xcodeproj/project.pbxproj +++ b/Nudge.xcodeproj/project.pbxproj @@ -698,7 +698,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.0.4; + MARKETING_VERSION = 2.0.5; PRODUCT_BUNDLE_IDENTIFIER = com.github.macadmins.Nudge; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -729,7 +729,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.0.4; + MARKETING_VERSION = 2.0.5; PRODUCT_BUNDLE_IDENTIFIER = com.github.macadmins.Nudge; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Nudge/Info.plist b/Nudge/Info.plist index 37a39d0b..31ab7d01 100644 --- a/Nudge/Info.plist +++ b/Nudge/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.0.4 + 2.0.5 CFBundleVersion - 2.0.4 + 2.0.5 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Nudge/Preferences/DefaultPreferencesNudge.swift b/Nudge/Preferences/DefaultPreferencesNudge.swift index 05f4808e..ad785943 100644 --- a/Nudge/Preferences/DefaultPreferencesNudge.swift +++ b/Nudge/Preferences/DefaultPreferencesNudge.swift @@ -233,6 +233,12 @@ struct OSVersionRequirementVariables { "" } + static var minorVersionRecalculationThreshold: Int { + osVersionRequirementsProfile?.minorVersionRecalculationThreshold ?? + osVersionRequirementsJSON?.minorVersionRecalculationThreshold ?? + 0 + } + static var nonActivelyExploitedCVEsMajorUpgradeSLA: Int { osVersionRequirementsProfile?.nonActivelyExploitedCVEsMajorUpgradeSLA ?? osVersionRequirementsJSON?.nonActivelyExploitedCVEsMajorUpgradeSLA ?? diff --git a/Nudge/Preferences/PreferencesStructure.swift b/Nudge/Preferences/PreferencesStructure.swift index 085b6e8b..65572c35 100644 --- a/Nudge/Preferences/PreferencesStructure.swift +++ b/Nudge/Preferences/PreferencesStructure.swift @@ -151,6 +151,7 @@ struct OSVersionRequirement: Codable { var activelyExploitedCVEsMajorUpgradeSLA: Int? var activelyExploitedCVEsMinorUpdateSLA: Int? var majorUpgradeAppPath: String? + var minorVersionRecalculationThreshold: Int? var nonActivelyExploitedCVEsMajorUpgradeSLA: Int? var nonActivelyExploitedCVEsMinorUpdateSLA: Int? var requiredInstallationDate: Date? @@ -170,6 +171,7 @@ extension OSVersionRequirement { self.activelyExploitedCVEsMajorUpgradeSLA = fromDictionary["activelyExploitedCVEsMajorUpgradeSLA"] as? Int self.activelyExploitedCVEsMinorUpdateSLA = fromDictionary["activelyExploitedCVEsMinorUpdateSLA"] as? Int self.majorUpgradeAppPath = fromDictionary["majorUpgradeAppPath"] as? String + self.minorVersionRecalculationThreshold = fromDictionary["minorVersionRecalculationThreshold"] as? Int self.nonActivelyExploitedCVEsMajorUpgradeSLA = fromDictionary["nonActivelyExploitedCVEsMajorUpgradeSLA"] as? Int self.nonActivelyExploitedCVEsMinorUpdateSLA = fromDictionary["nonActivelyExploitedCVEsMinorUpdateSLA"] as? Int self.requiredMinimumOSVersion = fromDictionary["requiredMinimumOSVersion"] as? String @@ -248,6 +250,7 @@ extension OSVersionRequirement { activelyExploitedCVEsMajorUpgradeSLA: Int? = nil, activelyExploitedCVEsMinorUpdateSLA: Int? = nil, majorUpgradeAppPath: String? = nil, + minorVersionRecalculationThreshold: Int? = nil, nonActivelyExploitedCVEsMajorUpgradeSLA: Int? = nil, nonActivelyExploitedCVEsMinorUpdateSLA: Int? = nil, requiredInstallationDate: Date? = nil, @@ -265,6 +268,7 @@ extension OSVersionRequirement { activelyExploitedCVEsMajorUpgradeSLA: activelyExploitedCVEsMajorUpgradeSLA ?? self.activelyExploitedCVEsMajorUpgradeSLA, activelyExploitedCVEsMinorUpdateSLA: activelyExploitedCVEsMinorUpdateSLA ?? self.activelyExploitedCVEsMinorUpdateSLA, majorUpgradeAppPath: majorUpgradeAppPath ?? self.majorUpgradeAppPath, + minorVersionRecalculationThreshold: minorVersionRecalculationThreshold ?? self.minorVersionRecalculationThreshold, nonActivelyExploitedCVEsMajorUpgradeSLA: nonActivelyExploitedCVEsMajorUpgradeSLA ?? self.nonActivelyExploitedCVEsMajorUpgradeSLA, nonActivelyExploitedCVEsMinorUpdateSLA: nonActivelyExploitedCVEsMinorUpdateSLA ?? self.nonActivelyExploitedCVEsMinorUpdateSLA, requiredInstallationDate: requiredInstallationDate ?? self.requiredInstallationDate, diff --git a/Nudge/UI/Main.swift b/Nudge/UI/Main.swift index 2190476b..dc7d2e20 100644 --- a/Nudge/UI/Main.swift +++ b/Nudge/UI/Main.swift @@ -178,6 +178,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { var foundMatch = false Globals.sofaAssets = NetworkFileManager().getSOFAAssets() if let macOSSOFAAssets = Globals.sofaAssets?.osVersions { + // Get current installed OS version + let currentInstalledVersion = GlobalVariables.currentOSVersion + let currentMajorVersion = VersionManager.getMajorVersion(from: currentInstalledVersion) + for osVersion in macOSSOFAAssets { if PrefsWrapper.requiredMinimumOSVersion == "latest" { selectedOS = osVersion.latest @@ -204,24 +208,64 @@ class AppDelegate: NSObject, NSApplicationDelegate { continue } } - let activelyExploitedCVEs = selectedOS!.activelyExploitedCVEs.count > 0 + + var totalActivelyExploitedCVEs = 0 + let selectedOSVersion = selectedOS!.productVersion + var allVersions = [String]() + + // Collect all versions + for osVersion in macOSSOFAAssets { + allVersions.append(osVersion.latest.productVersion) + for securityRelease in osVersion.securityReleases { + allVersions.append(securityRelease.productVersion) + } + } + + // Sort versions + allVersions.sort { VersionManager.versionLessThan(currentVersion: $0, newVersion: $1) } + + // Filter versions between current and selected OS version + let filteredVersions = VersionManager().removeDuplicates(from: allVersions.filter { + VersionManager.versionGreaterThanOrEqual(currentVersion: $0, newVersion: currentInstalledVersion) && + VersionManager.versionLessThanOrEqual(currentVersion: $0, newVersion: selectedOSVersion) + }) + + // Filter versions with the same major version as the current installed version + let minorVersions = VersionManager().removeDuplicates(from: filteredVersions.filter { version in + VersionManager.getMajorVersion(from: version) == currentMajorVersion + }) + + // Count actively exploited CVEs in the filtered versions + LogManager.notice("Assessing macOS version range for active exploits: \(filteredVersions) ", logger: sofaLog) + for osVersion in macOSSOFAAssets { + if filteredVersions.contains(osVersion.latest.productVersion) { + totalActivelyExploitedCVEs += osVersion.latest.activelyExploitedCVEs.count + } + for securityRelease in osVersion.securityReleases { + if filteredVersions.contains(securityRelease.productVersion) { + totalActivelyExploitedCVEs += securityRelease.activelyExploitedCVEs.count + } + } + } + let activelyExploitedCVEs = totalActivelyExploitedCVEs > 0 + let presentCVEs = selectedOS!.cves.count > 0 let slaExtension: TimeInterval switch (activelyExploitedCVEs, presentCVEs, AppStateManager().requireMajorUpgrade()) { - case (false, true, true): - slaExtension = TimeInterval(OSVersionRequirementVariables.nonActivelyExploitedCVEsMajorUpgradeSLA * 86400) - case (false, true, false): - slaExtension = TimeInterval(OSVersionRequirementVariables.nonActivelyExploitedCVEsMinorUpdateSLA * 86400) - case (true, true, true): - slaExtension = TimeInterval(OSVersionRequirementVariables.activelyExploitedCVEsMajorUpgradeSLA * 86400) - case (true, true, false): - slaExtension = TimeInterval(OSVersionRequirementVariables.activelyExploitedCVEsMinorUpdateSLA * 86400) - case (false, false, true): - slaExtension = TimeInterval(OSVersionRequirementVariables.standardMajorUpgradeSLA * 86400) - case (false, false, false): - slaExtension = TimeInterval(OSVersionRequirementVariables.standardMinorUpdateSLA * 86400) - default: // If we get here, something is wrong, use 90 days as a safety - slaExtension = TimeInterval(90 * 86400) + case (false, true, true): + slaExtension = TimeInterval(OSVersionRequirementVariables.nonActivelyExploitedCVEsMajorUpgradeSLA * 86400) + case (false, true, false): + slaExtension = TimeInterval(OSVersionRequirementVariables.nonActivelyExploitedCVEsMinorUpdateSLA * 86400) + case (true, true, true): + slaExtension = TimeInterval(OSVersionRequirementVariables.activelyExploitedCVEsMajorUpgradeSLA * 86400) + case (true, true, false): + slaExtension = TimeInterval(OSVersionRequirementVariables.activelyExploitedCVEsMinorUpdateSLA * 86400) + case (false, false, true): + slaExtension = TimeInterval(OSVersionRequirementVariables.standardMajorUpgradeSLA * 86400) + case (false, false, false): + slaExtension = TimeInterval(OSVersionRequirementVariables.standardMinorUpdateSLA * 86400) + default: // If we get here, something is wrong, use 90 days as a safety + slaExtension = TimeInterval(90 * 86400) } if OptionalFeatureVariables.disableNudgeForStandardInstalls && !presentCVEs { @@ -235,7 +279,32 @@ class AppDelegate: NSObject, NSApplicationDelegate { nudgePrimaryState.activelyExploitedCVEs = activelyExploitedCVEs releaseDate = selectedOS!.releaseDate ?? Date() if requiredInstallationDate == Date(timeIntervalSince1970: 0) { - requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400)) + if OSVersionRequirementVariables.minorVersionRecalculationThreshold > 0 { + if minorVersions.isEmpty { + requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400)) + } else { + let safeIndex = max(0, minorVersions.count - (OSVersionRequirementVariables.minorVersionRecalculationThreshold + 1)) // Ensure the index is within bounds + let targetVersion = minorVersions[safeIndex] + var foundVersion = false + LogManager.notice("minorVersionRecalculationThreshold is set to \(OSVersionRequirementVariables.minorVersionRecalculationThreshold) - Current Version: \(currentInstalledVersion) - Targeting version \(targetVersion) requiredInstallationDate via SOFA", logger: sofaLog) + for osVersion in macOSSOFAAssets { + for securityRelease in osVersion.securityReleases.reversed() { + if VersionManager.versionGreaterThanOrEqual(currentVersion: securityRelease.productVersion, newVersion: targetVersion) && VersionManager.versionLessThanOrEqual(currentVersion: currentInstalledVersion, newVersion: targetVersion) { + requiredInstallationDate = securityRelease.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400)) + LogManager.notice("Found target macOS version \(targetVersion) - releaseDate is \(securityRelease.releaseDate!), slaExtension is \(LoggerUtilities().printTimeInterval(slaExtension))", logger: sofaLog) + foundVersion = true + break + } + } + } + if !foundVersion { + LogManager.warning("Could not find requiredInstallationDate from target macOS \(targetVersion)", logger: sofaLog) + requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400)) + } + } + } else { + requiredInstallationDate = selectedOS!.releaseDate?.addingTimeInterval(slaExtension) ?? DateManager().getCurrentDate().addingTimeInterval(TimeInterval(90 * 86400)) + } LogManager.notice("Setting requiredInstallationDate via SOFA to \(requiredInstallationDate)", logger: sofaLog) } LogManager.notice("SOFA Matched OS Version: \(selectedOS!.productVersion)", logger: sofaLog) @@ -252,7 +321,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { supportedDevice in Globals.hardwareModelIDs.contains { $0.uppercased() == supportedDevice.uppercased() } } ) LogManager.notice("Assessed Model ID found in SOFA Entry: \(deviceMatchFound)", logger: sofaLog) - nudgePrimaryState.deviceSupportedByOSVersion = deviceMatchFound // false + nudgePrimaryState.deviceSupportedByOSVersion = deviceMatchFound } } foundMatch = true diff --git a/Nudge/Utilities/Utils.swift b/Nudge/Utilities/Utils.swift index 2b971f83..69bcf60f 100644 --- a/Nudge/Utilities/Utils.swift +++ b/Nudge/Utilities/Utils.swift @@ -553,10 +553,20 @@ struct DateManager { return formatter }() - func coerceDateToString(date: Date, formatterString: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = formatterString - return formatter.string(from: date) + func coerceDateToString(date: Date, formatterString: String, locale: Locale? = nil) -> String { + if formatterString == "MM/dd/yyyy" { + // Use the specified locale or the current locale if none is provided + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + dateFormatter.timeStyle = .none + dateFormatter.locale = locale ?? Locale.current + return dateFormatter.string(from: date) + } else { + let formatter = DateFormatter() + formatter.dateFormat = formatterString + formatter.locale = locale ?? Locale.current + return formatter.string(from: date) + } } func coerceStringToDate(dateString: String) -> Date { @@ -884,6 +894,15 @@ struct LoggerUtilities { return PrefsWrapper.requiredMinimumOSVersion == "0.0" } + func printTimeInterval(_ interval: TimeInterval) -> String { + let days = Int(interval) / (24 * 3600) + let hours = (Int(interval) % (24 * 3600)) / 3600 + let minutes = (Int(interval) % 3600) / 60 + let seconds = Int(interval) % 60 + + return "\(days) days, \(hours) hours, \(minutes) minutes, \(seconds) seconds" + } + private func updateDeferralCount(_ count: inout Int, resetCount: Bool, key: String) { if CommandLineUtilities().demoModeEnabled() { count = 0 @@ -1482,6 +1501,10 @@ struct VersionManager { return majorVersion } + static func getMajorVersion(from version: String) -> Int { + return Int(version.split(separator: ".").first.map(String.init)!)! + } + static func getMinorOSVersion() -> Int { var minorOSVersion = ProcessInfo().operatingSystemVersion.minorVersion // if (CommandLineUtilities().simulateOSVersion() != nil) { @@ -1506,6 +1529,12 @@ struct VersionManager { versionGreaterThan(currentVersion: nudgePrimaryState.requiredMinimumOSVersion, newVersion: nudgePrimaryState.userRequiredMinimumOSVersion) } + // Helper function to remove duplicates while preserving order + func removeDuplicates(from array: [String]) -> [String] { + var seen = Set() + return array.filter { seen.insert($0).inserted } + } + // Adapted from https://stackoverflow.com/a/25453654 static func versionEqual(currentVersion: String, newVersion: String) -> Bool { return currentVersion.compare(newVersion, options: .numeric) == .orderedSame diff --git a/Schema/jamf/com.github.macadmins.Nudge.json b/Schema/jamf/com.github.macadmins.Nudge.json index 00da3811..a5c16b5d 100644 --- a/Schema/jamf/com.github.macadmins.Nudge.json +++ b/Schema/jamf/com.github.macadmins.Nudge.json @@ -508,6 +508,25 @@ } ] }, + "minorVersionRecalculationThreshold": { + "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)", + "anyOf": [ + { + "title": "Not Configured", + "type": "null" + }, + { + "title": "Configured", + "default": 0, + "type": "integer", + "options": { + "inputAttributes": { + "placeholder": "0" + } + } + } + ] + }, "nonActivelyExploitedCVEsMajorUpgradeSLA": { "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)", "anyOf": [