diff --git a/Pareto Security.xcodeproj/project.pbxproj b/Pareto Security.xcodeproj/project.pbxproj index 5887f6a..2d14835 100644 --- a/Pareto Security.xcodeproj/project.pbxproj +++ b/Pareto Security.xcodeproj/project.pbxproj @@ -230,6 +230,8 @@ 4FDAD88F26EB784A00F64E61 /* License.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDAD88E26EB784A00F64E61 /* License.swift */; }; 4FE23EED276BB13700CA9BA0 /* FollowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE23EEC276BB13700CA9BA0 /* FollowView.swift */; }; 4FE23EEE276BB13700CA9BA0 /* FollowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE23EEC276BB13700CA9BA0 /* FollowView.swift */; }; + 4FE9C35A28B76117002C28FE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE9C35928B76117002C28FE /* User.swift */; }; + 4FE9C35B28B76117002C28FE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE9C35928B76117002C28FE /* User.swift */; }; 4FEBA2F2269CD48F009B2469 /* ParetoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEBA2F1269CD48F009B2469 /* ParetoApp.swift */; }; 4FEBA2F6269CD48F009B2469 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FEBA2F5269CD48F009B2469 /* Assets.xcassets */; }; 4FEBA2F9269CD48F009B2469 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FEBA2F8269CD48F009B2469 /* Preview Assets.xcassets */; }; @@ -371,6 +373,7 @@ 4FDA93A22771D9FC00F9D8B4 /* Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checks.swift; sourceTree = ""; }; 4FDAD88E26EB784A00F64E61 /* License.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = License.swift; sourceTree = ""; }; 4FE23EEC276BB13700CA9BA0 /* FollowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FollowView.swift; sourceTree = ""; }; + 4FE9C35928B76117002C28FE /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 4FEBA2EE269CD48F009B2469 /* Pareto Security.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pareto Security.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4FEBA2F1269CD48F009B2469 /* ParetoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParetoApp.swift; sourceTree = ""; }; 4FEBA2F5269CD48F009B2469 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -616,6 +619,7 @@ 4FB0416026F71CC0003657BE /* ButtonStyle.swift */, 4F5B20C2270316DB0015642A /* NetworkHandler.swift */, 4F9982FB281FC46600C5F54F /* Defaults+Workaround.swift */, + 4FE9C35928B76117002C28FE /* User.swift */, ); path = Extensions; sourceTree = ""; @@ -1031,6 +1035,7 @@ 4F37E7172718122E00A2B254 /* Color.swift in Sources */, 4F79F78E273BF6300082A5EB /* ChecksView.swift in Sources */, 4F37E7182718122E00A2B254 /* LicenseSettingsView.swift in Sources */, + 4FE9C35B28B76117002C28FE /* User.swift in Sources */, 4F37E7192718122E00A2B254 /* RemoteManagment.swift in Sources */, 4F37E71A2718122E00A2B254 /* Date.swift in Sources */, 4F56AB5F278C6DF5001CAAEF /* AdobeReader.swift in Sources */, @@ -1161,6 +1166,7 @@ 4F9791C126F5DB4F00E668F4 /* Color.swift in Sources */, 4F79F78D273BF6300082A5EB /* ChecksView.swift in Sources */, 4F24A08E26EB734A0036F748 /* LicenseSettingsView.swift in Sources */, + 4FE9C35A28B76117002C28FE /* User.swift in Sources */, 4F35395526E8AD4C008F5DD3 /* RemoteManagment.swift in Sources */, 4FB7C68126AFF25300FB1C41 /* Date.swift in Sources */, 4F56AB5E278C6DF5001CAAEF /* AdobeReader.swift in Sources */, diff --git a/Pareto/AppHandlers.swift b/Pareto/AppHandlers.swift index 9a1c8fb..69d8130 100644 --- a/Pareto/AppHandlers.swift +++ b/Pareto/AppHandlers.swift @@ -180,19 +180,26 @@ class AppHandlers: NSObject, NetworkHandlerObserver { } func checkForRelease() { - let currentVersion = Bundle.main.version - if let release = try? updater!.getLatestRelease() { - #if !SETAPP_ENABLED - if currentVersion < release.version { - if let zipURL = release.assets.filter({ $0.browser_download_url.path.hasSuffix(".zip") }).first { - let done = updater!.downloadAndUpdate(withAsset: zipURL) - // Failed to update - if !done { - Defaults[.updateNag] = true + if !SystemUser.current.isAdmin { + Defaults[.updateNag] = true + return + } + + DispatchQueue.global(qos: .userInteractive).async { [self] in + let currentVersion = Bundle.main.version + if let release = try? updater!.getLatestRelease() { + #if !SETAPP_ENABLED + if currentVersion < release.version { + if let zipURL = release.assets.filter({ $0.browser_download_url.path.hasSuffix(".zip") }).first { + let done = updater!.downloadAndUpdate(withAsset: zipURL) + // Failed to update + if !done { + Defaults[.updateNag] = true + } } } - } - #endif + #endif + } } } diff --git a/Pareto/AppInfo.swift b/Pareto/AppInfo.swift index 40f8fe2..66237da 100644 --- a/Pareto/AppInfo.swift +++ b/Pareto/AppInfo.swift @@ -116,7 +116,7 @@ enum AppInfo { logs.append("Location: \(Bundle.main.path)") logs.append("Build: \(AppInfo.utmSource)") - + logs.append("IsAdmin: \(SystemUser.current.isAdmin.description)") logs.append("\nLogs:") if #available(macOS 12.0, *) { diff --git a/Pareto/Extensions/User.swift b/Pareto/Extensions/User.swift new file mode 100644 index 0000000..c4464a0 --- /dev/null +++ b/Pareto/Extensions/User.swift @@ -0,0 +1,60 @@ +// +// User.swift +// Pareto Security +// +// Created by Janez Troha on 25/08/2022. +// +// https://stackoverflow.com/a/64034073 + +import Cocoa +import CoreServices +import Foundation + +enum QueryError: Swift.Error { + case queryExecutionFailed + case queriedWithoutResult +} + +struct SystemUser { + static let current = SystemUser() + + private func getUser() throws -> CSIdentity { + let query = CSIdentityQueryCreateForCurrentUser(kCFAllocatorDefault).takeRetainedValue() + let flags = CSIdentityQueryFlags() + guard CSIdentityQueryExecute(query, flags, nil) else { throw QueryError.queryExecutionFailed } + + let users = CSIdentityQueryCopyResults(query).takeRetainedValue() as! [CSIdentity] + + guard let currentUser = users.first else { throw QueryError.queriedWithoutResult } + + return currentUser + } + + private func getAdminGroup() throws -> CSIdentity { + let privilegeGroup = "admin" as CFString + let authority = CSGetDefaultIdentityAuthority().takeRetainedValue() + let query = CSIdentityQueryCreateForName(kCFAllocatorDefault, + privilegeGroup, + kCSIdentityQueryStringEquals, + kCSIdentityClassGroup, + authority).takeRetainedValue() + let flags = CSIdentityQueryFlags() + + guard CSIdentityQueryExecute(query, flags, nil) else { throw QueryError.queryExecutionFailed } + let groups = CSIdentityQueryCopyResults(query).takeRetainedValue() as! [CSIdentity] + + guard let adminGroup = groups.first else { throw QueryError.queriedWithoutResult } + + return adminGroup + } + + var isAdmin: Bool { + do { + let user = try getUser() + let group = try getAdminGroup() + return CSIdentityIsMemberOfGroup(user, group) + } catch { + return false + } + } +} diff --git a/Pareto/Info.plist b/Pareto/Info.plist index a171ac6..70f2d83 100644 --- a/Pareto/Info.plist +++ b/Pareto/Info.plist @@ -26,7 +26,7 @@ CFBundleVersion - 4971 + 4974 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Pareto/ParetoApp.swift b/Pareto/ParetoApp.swift index fa7d630..a648545 100644 --- a/Pareto/ParetoApp.swift +++ b/Pareto/ParetoApp.swift @@ -23,6 +23,10 @@ class AppDelegate: AppHandlers, NSApplicationDelegate { var export: [String: [String: [String]]] = [:] for claim in Claims.sorted { + // skip empty claims + if claim.checks.count == 0 { + continue + } var claimExport: [String: [String]] = [:] for check in claim.checksSorted { claimExport[check.UUID] = [check.TitleON, check.TitleOFF] diff --git a/Pareto/Views/Settings/AboutSettingsView.swift b/Pareto/Views/Settings/AboutSettingsView.swift index 336a782..d833f9f 100644 --- a/Pareto/Views/Settings/AboutSettingsView.swift +++ b/Pareto/Views/Settings/AboutSettingsView.swift @@ -72,6 +72,11 @@ struct AboutSettingsView: View { } private func fetch() { + if !SystemUser.current.isAdmin { + status = UpdateStates.Failed + return + } + #if !SETAPP_ENABLED DispatchQueue.global(qos: .userInteractive).async { isLoading = true