From 5cf43c1b762fc2c67f9c33f92b8b83e1ba2c46e9 Mon Sep 17 00:00:00 2001 From: Janez T Date: Thu, 14 Mar 2024 14:16:36 +0100 Subject: [PATCH] Add SBOM list generator --- .../Access Security/NoUnusedUsers.swift | 2 +- Pareto/Info.plist | 2 +- Pareto/Teams.swift | 3 +- Pareto/Views/Settings/TeamSettings.swift | 95 ++++++++++++++++++- 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/Pareto/Checks/Access Security/NoUnusedUsers.swift b/Pareto/Checks/Access Security/NoUnusedUsers.swift index a5d9657..9f55554 100644 --- a/Pareto/Checks/Access Security/NoUnusedUsers.swift +++ b/Pareto/Checks/Access Security/NoUnusedUsers.swift @@ -18,7 +18,7 @@ class NoUnusedUsers: ParetoCheck { } override var TitleOFF: String { - "Unused user accounts are present ("+accounts.joined(separator: ",")+")" + "Unused user accounts are present (" + accounts.joined(separator: ",") + ")" } var accounts: [String] { diff --git a/Pareto/Info.plist b/Pareto/Info.plist index a3fce07..1a2d09e 100644 --- a/Pareto/Info.plist +++ b/Pareto/Info.plist @@ -26,7 +26,7 @@ CFBundleVersion - 5429 + 5439 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Pareto/Teams.swift b/Pareto/Teams.swift index 83dc7d3..dcaba8e 100644 --- a/Pareto/Teams.swift +++ b/Pareto/Teams.swift @@ -298,12 +298,11 @@ class TeamSettingsUpdater: ObservableObject { if Defaults[.appliedIgnoredChecks] { NoUnusedUsers.sharedInstance.isActive = false NoAdminUser.sharedInstance.isActive = false - }else { + } else { check.isActive = false } Defaults[.appliedIgnoredChecksIDs].append(check.UUID) } - } } } diff --git a/Pareto/Views/Settings/TeamSettings.swift b/Pareto/Views/Settings/TeamSettings.swift index b655e47..8a6e4fb 100644 --- a/Pareto/Views/Settings/TeamSettings.swift +++ b/Pareto/Views/Settings/TeamSettings.swift @@ -8,6 +8,77 @@ import Defaults import SwiftUI +struct PublicApp: Codable { + let name: String + let bundle: String + let version: String + + static var all: [PublicApp] { + var detectedApps: [PublicApp] = [] + let allApps = try! FileManager.default.contentsOfDirectory(at: URL(string: "/Applications")!, includingPropertiesForKeys: [.isApplicationKey]) + for app in allApps { + let plist = PublicApp.readPlistFile(fileURL: app.appendingPathComponent("Contents/Info.plist")) + if let appName = plist?["CFBundleName"] as? String, + let appBundle = plist?["CFBundleIdentifier"] as? String { + let bundleApp = PublicApp( + name: appName, + bundle: appBundle, + version: plist?["CFBundleShortVersionString"] as? String ?? "Unknown" + ) + detectedApps.append(bundleApp) + } + } + + // user apps + let homeDirURL = FileManager.default.homeDirectoryForCurrentUser + let localPath = URL(fileURLWithPath: "\(homeDirURL.path)/Applications/") + if (try? localPath.checkResourceIsReachable()) ?? false { + let userApps = try! FileManager.default.contentsOfDirectory(at: localPath, includingPropertiesForKeys: [.isApplicationKey]) + for app in userApps { + let plist = PublicApp.readPlistFile(fileURL: app.appendingPathComponent("Contents/Info.plist")) + if let appName = plist?["CFBundleName"] as? String, + let appBundle = plist?["CFBundleIdentifier"] as? String { + let bundleApp = PublicApp( + name: appName, + bundle: appBundle, + version: plist?["CFBundleShortVersionString"] as? String ?? "Unknown" + ) + detectedApps.append(bundleApp) + } + } + } + + return detectedApps + } + + static func readPlistFile(fileURL: URL) -> [String: Any]? { + guard let data = try? Data(contentsOf: fileURL) else { + return nil + } + guard let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { + return nil + } + return result + } + + static func asJSON() -> String? { + var export: [PublicApp] = [] + + for app in PublicApp.all.sorted(by: { lha, rha in + lha.name.lowercased() < rha.name.lowercased() + }) { + export.append(app) + } + + let jsonEncoder = JSONEncoder() + jsonEncoder.outputFormatting = .prettyPrinted + let jsonData = try! jsonEncoder.encode(export) + guard let json = String(data: jsonData, encoding: String.Encoding.utf8) else { return nil } + + return json + } +} + struct TeamSettingsView: View { @StateObject var teamSettings: TeamSettingsUpdater @@ -25,6 +96,22 @@ struct TeamSettingsView: View { NSPasteboard.general.setString("Team ID: \(teamID)\nMachine UUID: \(machineUUID)", forType: .string) } + func copyApps() { + var logs = [String]() + logs.append("Name, Bundle, Version") + for app in PublicApp.all { + logs.append("\(app.name), \(app.bundle), \(app.version)") + } + + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(logs.joined(separator: "\n"), forType: .string) + let alert = NSAlert() + alert.messageText = "List of installed apps has been copied to the clipboard." + alert.alertStyle = NSAlert.Style.informational + alert.addButton(withTitle: "OK") + alert.runModal() + } + func help() { NSWorkspace.shared.open(URL(string: "https://support.apple.com/en-ie/guide/mac-help/mchlp2322/mac#mchl8c79215b")!) } @@ -76,6 +163,11 @@ struct TeamSettingsView: View { } else { Toggle("Send inventory info on update", isOn: $sendHWInfo) } + HStack { + Button("Copy App list") { + copyApps() + } + } } } @@ -86,7 +178,8 @@ struct TeamSettingsView: View { Link("Team Dashboard ยป", destination: AppInfo.teamsURL()) } - }.frame(width: 380, height: 250).padding(25).onAppear { + Spacer(minLength: 2) + }.frame(width: 380, height: 290).padding(25).onAppear { DispatchQueue.main.async { teamSettings.update {} }