├── .gitignore ├── SwiftJamfAPI-1 ├── CLI Tool │ ├── JamfAuthToken.swift │ └── JamfList.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── armin.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftJamfAPI-2 ├── CLI Tool │ ├── Category.swift │ ├── Computer.swift │ ├── JamfAuthToken.swift │ └── JamfList.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── armin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftJamfAPI-3A ├── CLI Tool │ ├── Category.swift │ ├── Computer.swift │ ├── JamfAPIError.swift │ ├── JamfAuthToken.swift │ └── JamfList.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── armin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftJamfAPI-3B ├── CLI Tool │ └── JamfList.swift ├── Category.swift ├── Computer.swift ├── JamfAPIError.swift ├── JamfAuthToken.swift ├── JamfObject.swift ├── Script.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── armin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftJamfAPI-4 ├── CLI Tool │ ├── JamfList.swift │ └── Keychain.swift ├── Category.swift ├── Computer.swift ├── JamfAPIError.swift ├── JamfAuthToken.swift ├── JamfObject.swift ├── Script.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ └── xcschemes │ │ └── jamf_list.xcscheme │ └── xcuserdata │ └── armin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftJamfAPI-5 ├── CLI Tool │ ├── JamfList.swift │ └── Keychain.swift ├── Category.swift ├── Computer-Sample.swift ├── Computer.swift ├── JamfAPIError.swift ├── JamfAuthToken.swift ├── JamfList │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── DetailView.swift │ ├── JamfList.entitlements │ ├── JamfListApp.swift │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── JamfObject.swift ├── Script.swift └── SwiftJamfAPI.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── armin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ └── xcschemes │ │ └── jamf_list.xcscheme │ └── xcuserdata │ └── armin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── SwiftJamfAPI-6 ├── CLI Tool ├── JamfList.swift └── Keychain.swift ├── Category.swift ├── Computer.swift ├── JamfAPIError.swift ├── JamfAuthToken.swift ├── JamfList ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── ConnectSheet.swift ├── ContentView.swift ├── DetailView.swift ├── JamfController.swift ├── JamfList.entitlements ├── JamfListApp.swift └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── JamfObject.swift ├── Script.swift └── SwiftJamfAPI.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── armin.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── xcshareddata └── xcschemes │ └── jamf_list.xcscheme └── xcuserdata └── armin.xcuserdatad ├── xcdebugger └── Breakpoints_v2.xcbkptlist └── xcschemes └── xcschememanagement.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | SwiftJamfAPI working 3 | SwiftJamfAPI Patch Test 4 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/CLI Tool/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | } 14 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | static func main() async { 13 | let server = "https://jamf.example.com" 14 | let username = "api_user" 15 | 16 | // TODO: get this from Keychain 17 | let password = "secret_password" 18 | 19 | // MARK: Get Jamf Auth Token 20 | 21 | // MARK: Prepare Request 22 | // encode user name and password 23 | let base64 = "\(username):\(password)" 24 | .data(using: String.Encoding.utf8)! 25 | .base64EncodedString() 26 | 27 | // assemble the URL for the Jamf API 28 | guard var components = URLComponents(string: server) else { 29 | exit(1) 30 | } 31 | components.path="/api/v1/auth/token" 32 | guard let url = components.url else { 33 | exit(1) 34 | } 35 | 36 | // MARK: Send Request and get Data 37 | 38 | // create the request 39 | var authRequest = URLRequest(url: url) 40 | authRequest.httpMethod = "POST" 41 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 42 | 43 | // send request and get data 44 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 45 | else { 46 | exit(1) 47 | } 48 | 49 | // MARK: Handle Errors 50 | 51 | // check the response code 52 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 53 | if authStatusCode != 200 { 54 | exit(1) 55 | } 56 | 57 | // print(String(data: data, encoding: .utf8) ?? "") 58 | 59 | 60 | // MARK: Parse JSON returned 61 | let decoder = JSONDecoder() 62 | 63 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 64 | else { 65 | exit(1) 66 | } 67 | 68 | // print("Token: \(auth.token)") 69 | // print("Expires: \(auth.expires)") 70 | 71 | // MARK: Get Categories 72 | 73 | // MARK: Prepare Request 74 | // assemble the URL for the Jamf API 75 | guard var components = URLComponents(string: server) 76 | else { 77 | exit(1) 78 | } 79 | components.path="/api/v1/categories" 80 | guard let url = components.url 81 | else { 82 | exit(1) 83 | } 84 | 85 | // MARK: Send Request and get Data 86 | // create the request 87 | var request = URLRequest(url: url) 88 | request.httpMethod = "GET" 89 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 90 | 91 | // send request and get data 92 | guard let (data, response) = try? await URLSession.shared.data(for: request) 93 | else { 94 | exit(1) 95 | } 96 | 97 | // MARK: Handle Error 98 | // check the response code 99 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 100 | if statusCode != 200 { 101 | // error getting token 102 | exit(1) 103 | } 104 | 105 | print(String(data: data, encoding: .utf8) ?? "") 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 11 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 29 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | C6F53D6C28C6295600E0B11F = { 44 | isa = PBXGroup; 45 | children = ( 46 | C6F53D7728C6295600E0B11F /* CLI Tool */, 47 | C6F53D7628C6295600E0B11F /* Products */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | C6F53D7628C6295600E0B11F /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | C6F53D7528C6295600E0B11F /* jamf_list */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 63 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 64 | ); 65 | path = "CLI Tool"; 66 | sourceTree = ""; 67 | }; 68 | /* End PBXGroup section */ 69 | 70 | /* Begin PBXNativeTarget section */ 71 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 72 | isa = PBXNativeTarget; 73 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 74 | buildPhases = ( 75 | C6F53D7128C6295600E0B11F /* Sources */, 76 | C6F53D7228C6295600E0B11F /* Frameworks */, 77 | C6F53D7328C6295600E0B11F /* CopyFiles */, 78 | ); 79 | buildRules = ( 80 | ); 81 | dependencies = ( 82 | ); 83 | name = jamf_list; 84 | productName = SwiftJamfAPI; 85 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 86 | productType = "com.apple.product-type.tool"; 87 | }; 88 | /* End PBXNativeTarget section */ 89 | 90 | /* Begin PBXProject section */ 91 | C6F53D6D28C6295600E0B11F /* Project object */ = { 92 | isa = PBXProject; 93 | attributes = { 94 | BuildIndependentTargetsInParallel = 1; 95 | LastSwiftUpdateCheck = 1340; 96 | LastUpgradeCheck = 1340; 97 | TargetAttributes = { 98 | C6F53D7428C6295600E0B11F = { 99 | CreatedOnToolsVersion = 13.4.1; 100 | LastSwiftMigration = 1340; 101 | }; 102 | }; 103 | }; 104 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 105 | compatibilityVersion = "Xcode 13.0"; 106 | developmentRegion = en; 107 | hasScannedForEncodings = 0; 108 | knownRegions = ( 109 | en, 110 | Base, 111 | ); 112 | mainGroup = C6F53D6C28C6295600E0B11F; 113 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | C6F53D7428C6295600E0B11F /* jamf_list */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXSourcesBuildPhase section */ 123 | C6F53D7128C6295600E0B11F /* Sources */ = { 124 | isa = PBXSourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 128 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXSourcesBuildPhase section */ 133 | 134 | /* Begin XCBuildConfiguration section */ 135 | C6F53D7A28C6295600E0B11F /* Debug */ = { 136 | isa = XCBuildConfiguration; 137 | buildSettings = { 138 | ALWAYS_SEARCH_USER_PATHS = NO; 139 | CLANG_ANALYZER_NONNULL = YES; 140 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 141 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 142 | CLANG_ENABLE_MODULES = YES; 143 | CLANG_ENABLE_OBJC_ARC = YES; 144 | CLANG_ENABLE_OBJC_WEAK = YES; 145 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 146 | CLANG_WARN_BOOL_CONVERSION = YES; 147 | CLANG_WARN_COMMA = YES; 148 | CLANG_WARN_CONSTANT_CONVERSION = YES; 149 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 150 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 151 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 152 | CLANG_WARN_EMPTY_BODY = YES; 153 | CLANG_WARN_ENUM_CONVERSION = YES; 154 | CLANG_WARN_INFINITE_RECURSION = YES; 155 | CLANG_WARN_INT_CONVERSION = YES; 156 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 157 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 158 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 159 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 160 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 161 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 162 | CLANG_WARN_STRICT_PROTOTYPES = YES; 163 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 164 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 165 | CLANG_WARN_UNREACHABLE_CODE = YES; 166 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 167 | COPY_PHASE_STRIP = NO; 168 | DEBUG_INFORMATION_FORMAT = dwarf; 169 | ENABLE_STRICT_OBJC_MSGSEND = YES; 170 | ENABLE_TESTABILITY = YES; 171 | GCC_C_LANGUAGE_STANDARD = gnu11; 172 | GCC_DYNAMIC_NO_PIC = NO; 173 | GCC_NO_COMMON_BLOCKS = YES; 174 | GCC_OPTIMIZATION_LEVEL = 0; 175 | GCC_PREPROCESSOR_DEFINITIONS = ( 176 | "DEBUG=1", 177 | "$(inherited)", 178 | ); 179 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 180 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 181 | GCC_WARN_UNDECLARED_SELECTOR = YES; 182 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 183 | GCC_WARN_UNUSED_FUNCTION = YES; 184 | GCC_WARN_UNUSED_VARIABLE = YES; 185 | MACOSX_DEPLOYMENT_TARGET = 12.0; 186 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 187 | MTL_FAST_MATH = YES; 188 | ONLY_ACTIVE_ARCH = YES; 189 | SDKROOT = macosx; 190 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 191 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 192 | }; 193 | name = Debug; 194 | }; 195 | C6F53D7B28C6295600E0B11F /* Release */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_ANALYZER_NONNULL = YES; 200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 202 | CLANG_ENABLE_MODULES = YES; 203 | CLANG_ENABLE_OBJC_ARC = YES; 204 | CLANG_ENABLE_OBJC_WEAK = YES; 205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_COMMA = YES; 208 | CLANG_WARN_CONSTANT_CONVERSION = YES; 209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 212 | CLANG_WARN_EMPTY_BODY = YES; 213 | CLANG_WARN_ENUM_CONVERSION = YES; 214 | CLANG_WARN_INFINITE_RECURSION = YES; 215 | CLANG_WARN_INT_CONVERSION = YES; 216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 222 | CLANG_WARN_STRICT_PROTOTYPES = YES; 223 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 229 | ENABLE_NS_ASSERTIONS = NO; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu11; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | MACOSX_DEPLOYMENT_TARGET = 12.0; 240 | MTL_ENABLE_DEBUG_INFO = NO; 241 | MTL_FAST_MATH = YES; 242 | SDKROOT = macosx; 243 | SWIFT_COMPILATION_MODE = wholemodule; 244 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 245 | }; 246 | name = Release; 247 | }; 248 | C6F53D7D28C6295600E0B11F /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | CLANG_ENABLE_MODULES = YES; 252 | CODE_SIGN_STYLE = Automatic; 253 | DEVELOPMENT_TEAM = JME5BW3F3R; 254 | ENABLE_HARDENED_RUNTIME = YES; 255 | LD_RUNPATH_SEARCH_PATHS = ( 256 | "$(inherited)", 257 | "@executable_path/../Frameworks", 258 | "@loader_path/../Frameworks", 259 | ); 260 | PRODUCT_NAME = "$(TARGET_NAME)"; 261 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 262 | SWIFT_VERSION = 5.0; 263 | }; 264 | name = Debug; 265 | }; 266 | C6F53D7E28C6295600E0B11F /* Release */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | CLANG_ENABLE_MODULES = YES; 270 | CODE_SIGN_STYLE = Automatic; 271 | DEVELOPMENT_TEAM = JME5BW3F3R; 272 | ENABLE_HARDENED_RUNTIME = YES; 273 | LD_RUNPATH_SEARCH_PATHS = ( 274 | "$(inherited)", 275 | "@executable_path/../Frameworks", 276 | "@loader_path/../Frameworks", 277 | ); 278 | PRODUCT_NAME = "$(TARGET_NAME)"; 279 | SWIFT_VERSION = 5.0; 280 | }; 281 | name = Release; 282 | }; 283 | /* End XCBuildConfiguration section */ 284 | 285 | /* Begin XCConfigurationList section */ 286 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | C6F53D7A28C6295600E0B11F /* Debug */, 290 | C6F53D7B28C6295600E0B11F /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | C6F53D7D28C6295600E0B11F /* Debug */, 299 | C6F53D7E28C6295600E0B11F /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | /* End XCConfigurationList section */ 305 | }; 306 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 307 | } 308 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-1/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftJamfAPI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | jamf_list.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/CLI Tool/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: Codable { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | } 15 | 16 | struct CategoryResults: Codable { 17 | var totalCount: Int 18 | var results: [Category] 19 | } 20 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/CLI Tool/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: Codable { 11 | struct General: Codable { 12 | var name: String 13 | var assetTag: String? 14 | var lastEnrolledDate: Date 15 | var userApprovedMdm: Bool 16 | } 17 | 18 | struct Hardware: Codable { 19 | var model: String 20 | var modelIdentifier: String 21 | var serialNumber: String 22 | var appleSilicon: Bool 23 | } 24 | 25 | struct OperatingSystem: Codable { 26 | var name: String 27 | var version: String 28 | var build: String 29 | } 30 | 31 | var id: String 32 | var general: General 33 | var hardware: Hardware 34 | var operatingSystem: OperatingSystem 35 | } 36 | 37 | struct ComputerResults: Codable { 38 | var totalCount: Int 39 | var results: [Computer] 40 | } 41 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/CLI Tool/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | } 14 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | static func main() async { 13 | let server = "https://jamf.example.com" 14 | let username = "api_user" 15 | 16 | // TODO: get this from Keychain 17 | let password = "secret_password" 18 | 19 | // MARK: Get Jamf Auth Token 20 | 21 | // MARK: Prepare Request 22 | // encode user name and password 23 | let base64 = "\(username):\(password)" 24 | .data(using: String.Encoding.utf8)! 25 | .base64EncodedString() 26 | 27 | // assemble the URL for the Jamf API 28 | guard var components = URLComponents(string: server) else { 29 | exit(1) 30 | } 31 | components.path="/api/v1/auth/token" 32 | guard let url = components.url else { 33 | exit(1) 34 | } 35 | 36 | // MARK: Send Request and get Data 37 | 38 | // create the request 39 | var authRequest = URLRequest(url: url) 40 | authRequest.httpMethod = "POST" 41 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 42 | 43 | // send request and get data 44 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 45 | else { 46 | exit(1) 47 | } 48 | 49 | // MARK: Handle Errors 50 | 51 | // check the response code 52 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 53 | if authStatusCode != 200 { 54 | exit(1) 55 | } 56 | 57 | // print(String(data: data, encoding: .utf8) ?? "no data") 58 | 59 | 60 | // MARK: Parse JSON returned 61 | let decoder = JSONDecoder() 62 | 63 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 64 | else { 65 | exit(1) 66 | } 67 | 68 | // print("Token: \(auth.token)") 69 | // print("Expires: \(auth.expires)") 70 | 71 | // MARK: Get Computers 72 | 73 | // MARK: Prepare Request 74 | // assemble the URL for the Jamf API 75 | guard var components = URLComponents(string: server) 76 | else { 77 | exit(1) 78 | } 79 | components.path="/api/v1/computers-inventory" 80 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 81 | URLQueryItem(name: "section", value: "HARDWARE"), 82 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 83 | URLQueryItem(name: "sort", value: "id:asc") ] 84 | 85 | guard let url = components.url 86 | else { 87 | exit(1) 88 | } 89 | 90 | print("Request URL: \(url.absoluteString)") 91 | 92 | // MARK: Send Request and get Data 93 | // create the request 94 | var request = URLRequest(url: url) 95 | request.httpMethod = "GET" 96 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 97 | 98 | // send request and get data 99 | guard let (data, response) = try? await URLSession.shared.data(for: request) 100 | else { 101 | exit(1) 102 | } 103 | 104 | // MARK: Handle Error 105 | // check the response code 106 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 107 | if statusCode != 200 { 108 | // error getting token 109 | exit(1) 110 | } 111 | 112 | // print(String(data: data, encoding: .utf8) ?? "no data") 113 | 114 | // MARK: Parse JSON Data 115 | // set date decoding to match Jamf's date format 116 | let dateFormatter = DateFormatter() 117 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 118 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 119 | 120 | 121 | guard let result = try? decoder.decode(ComputerResults.self, from: data) 122 | else { 123 | exit(1) 124 | } 125 | 126 | for computer in result.results { 127 | print(computer.id, 128 | computer.general.name, 129 | computer.operatingSystem.version, 130 | computer.hardware.model) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C6873FB528D8A37000FA893B /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB428D8A37000FA893B /* Category.swift */; }; 11 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 12 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 13 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | ); 24 | runOnlyForDeploymentPostprocessing = 1; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | C6873FB428D8A37000FA893B /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 30 | C6873FB628D8A4D100FA893B /* Computer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Computer.swift; sourceTree = ""; }; 31 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 33 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | C6F53D6C28C6295600E0B11F = { 48 | isa = PBXGroup; 49 | children = ( 50 | C6F53D7728C6295600E0B11F /* CLI Tool */, 51 | C6F53D7628C6295600E0B11F /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | C6F53D7628C6295600E0B11F /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | C6F53D7528C6295600E0B11F /* jamf_list */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 67 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 68 | C6873FB628D8A4D100FA893B /* Computer.swift */, 69 | C6873FB428D8A37000FA893B /* Category.swift */, 70 | ); 71 | path = "CLI Tool"; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 80 | buildPhases = ( 81 | C6F53D7128C6295600E0B11F /* Sources */, 82 | C6F53D7228C6295600E0B11F /* Frameworks */, 83 | C6F53D7328C6295600E0B11F /* CopyFiles */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | name = jamf_list; 90 | productName = SwiftJamfAPI; 91 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 92 | productType = "com.apple.product-type.tool"; 93 | }; 94 | /* End PBXNativeTarget section */ 95 | 96 | /* Begin PBXProject section */ 97 | C6F53D6D28C6295600E0B11F /* Project object */ = { 98 | isa = PBXProject; 99 | attributes = { 100 | BuildIndependentTargetsInParallel = 1; 101 | LastSwiftUpdateCheck = 1340; 102 | LastUpgradeCheck = 1400; 103 | TargetAttributes = { 104 | C6F53D7428C6295600E0B11F = { 105 | CreatedOnToolsVersion = 13.4.1; 106 | LastSwiftMigration = 1340; 107 | }; 108 | }; 109 | }; 110 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 111 | compatibilityVersion = "Xcode 13.0"; 112 | developmentRegion = en; 113 | hasScannedForEncodings = 0; 114 | knownRegions = ( 115 | en, 116 | Base, 117 | ); 118 | mainGroup = C6F53D6C28C6295600E0B11F; 119 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 120 | projectDirPath = ""; 121 | projectRoot = ""; 122 | targets = ( 123 | C6F53D7428C6295600E0B11F /* jamf_list */, 124 | ); 125 | }; 126 | /* End PBXProject section */ 127 | 128 | /* Begin PBXSourcesBuildPhase section */ 129 | C6F53D7128C6295600E0B11F /* Sources */ = { 130 | isa = PBXSourcesBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */, 134 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 135 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 136 | C6873FB528D8A37000FA893B /* Category.swift in Sources */, 137 | ); 138 | runOnlyForDeploymentPostprocessing = 0; 139 | }; 140 | /* End PBXSourcesBuildPhase section */ 141 | 142 | /* Begin XCBuildConfiguration section */ 143 | C6F53D7A28C6295600E0B11F /* Debug */ = { 144 | isa = XCBuildConfiguration; 145 | buildSettings = { 146 | ALWAYS_SEARCH_USER_PATHS = NO; 147 | CLANG_ANALYZER_NONNULL = YES; 148 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 149 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 150 | CLANG_ENABLE_MODULES = YES; 151 | CLANG_ENABLE_OBJC_ARC = YES; 152 | CLANG_ENABLE_OBJC_WEAK = YES; 153 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 154 | CLANG_WARN_BOOL_CONVERSION = YES; 155 | CLANG_WARN_COMMA = YES; 156 | CLANG_WARN_CONSTANT_CONVERSION = YES; 157 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 158 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 159 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 160 | CLANG_WARN_EMPTY_BODY = YES; 161 | CLANG_WARN_ENUM_CONVERSION = YES; 162 | CLANG_WARN_INFINITE_RECURSION = YES; 163 | CLANG_WARN_INT_CONVERSION = YES; 164 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 165 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 166 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 167 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 168 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 169 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 170 | CLANG_WARN_STRICT_PROTOTYPES = YES; 171 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 172 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 173 | CLANG_WARN_UNREACHABLE_CODE = YES; 174 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 175 | COPY_PHASE_STRIP = NO; 176 | DEAD_CODE_STRIPPING = YES; 177 | DEBUG_INFORMATION_FORMAT = dwarf; 178 | ENABLE_STRICT_OBJC_MSGSEND = YES; 179 | ENABLE_TESTABILITY = YES; 180 | GCC_C_LANGUAGE_STANDARD = gnu11; 181 | GCC_DYNAMIC_NO_PIC = NO; 182 | GCC_NO_COMMON_BLOCKS = YES; 183 | GCC_OPTIMIZATION_LEVEL = 0; 184 | GCC_PREPROCESSOR_DEFINITIONS = ( 185 | "DEBUG=1", 186 | "$(inherited)", 187 | ); 188 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 189 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 190 | GCC_WARN_UNDECLARED_SELECTOR = YES; 191 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 192 | GCC_WARN_UNUSED_FUNCTION = YES; 193 | GCC_WARN_UNUSED_VARIABLE = YES; 194 | MACOSX_DEPLOYMENT_TARGET = 12.0; 195 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 196 | MTL_FAST_MATH = YES; 197 | ONLY_ACTIVE_ARCH = YES; 198 | SDKROOT = macosx; 199 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 200 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 201 | }; 202 | name = Debug; 203 | }; 204 | C6F53D7B28C6295600E0B11F /* Release */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_ENABLE_OBJC_WEAK = YES; 214 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_COMMA = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INFINITE_RECURSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 227 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 230 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 231 | CLANG_WARN_STRICT_PROTOTYPES = YES; 232 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 233 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 234 | CLANG_WARN_UNREACHABLE_CODE = YES; 235 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 236 | COPY_PHASE_STRIP = NO; 237 | DEAD_CODE_STRIPPING = YES; 238 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 239 | ENABLE_NS_ASSERTIONS = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu11; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 245 | GCC_WARN_UNDECLARED_SELECTOR = YES; 246 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 247 | GCC_WARN_UNUSED_FUNCTION = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | MACOSX_DEPLOYMENT_TARGET = 12.0; 250 | MTL_ENABLE_DEBUG_INFO = NO; 251 | MTL_FAST_MATH = YES; 252 | SDKROOT = macosx; 253 | SWIFT_COMPILATION_MODE = wholemodule; 254 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 255 | }; 256 | name = Release; 257 | }; 258 | C6F53D7D28C6295600E0B11F /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | CLANG_ENABLE_MODULES = YES; 262 | CODE_SIGN_IDENTITY = "-"; 263 | CODE_SIGN_STYLE = Automatic; 264 | DEAD_CODE_STRIPPING = YES; 265 | DEVELOPMENT_TEAM = JME5BW3F3R; 266 | ENABLE_HARDENED_RUNTIME = YES; 267 | LD_RUNPATH_SEARCH_PATHS = ( 268 | "$(inherited)", 269 | "@executable_path/../Frameworks", 270 | "@loader_path/../Frameworks", 271 | ); 272 | PRODUCT_NAME = "$(TARGET_NAME)"; 273 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 274 | SWIFT_VERSION = 5.0; 275 | }; 276 | name = Debug; 277 | }; 278 | C6F53D7E28C6295600E0B11F /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | CLANG_ENABLE_MODULES = YES; 282 | CODE_SIGN_IDENTITY = "-"; 283 | CODE_SIGN_STYLE = Automatic; 284 | DEAD_CODE_STRIPPING = YES; 285 | DEVELOPMENT_TEAM = JME5BW3F3R; 286 | ENABLE_HARDENED_RUNTIME = YES; 287 | LD_RUNPATH_SEARCH_PATHS = ( 288 | "$(inherited)", 289 | "@executable_path/../Frameworks", 290 | "@loader_path/../Frameworks", 291 | ); 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_VERSION = 5.0; 294 | }; 295 | name = Release; 296 | }; 297 | /* End XCBuildConfiguration section */ 298 | 299 | /* Begin XCConfigurationList section */ 300 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | C6F53D7A28C6295600E0B11F /* Debug */, 304 | C6F53D7B28C6295600E0B11F /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | C6F53D7D28C6295600E0B11F /* Debug */, 313 | C6F53D7E28C6295600E0B11F /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | /* End XCConfigurationList section */ 319 | }; 320 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 321 | } 322 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-2/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftJamfAPI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | jamf_list.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/CLI Tool/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: Codable { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | 15 | /** Gets a list of all categories from the Jamf Pro Server */ 16 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Category] { 17 | // MARK: Prepare Request 18 | // assemble the URL for the Jamf API 19 | guard var components = URLComponents(string: server) 20 | else { 21 | throw JamfAPIError.badURL 22 | } 23 | components.path="/api/v1/categories" 24 | 25 | guard let url = components.url 26 | else { 27 | throw JamfAPIError.badURL 28 | } 29 | 30 | // print("Request URL: \(url.absoluteString)") 31 | 32 | // MARK: Send Request and get Data 33 | // create the request 34 | var request = URLRequest(url: url) 35 | request.httpMethod = "GET" 36 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 37 | 38 | // send request and get data 39 | guard let (data, response) = try? await URLSession.shared.data(for: request) 40 | else { 41 | throw JamfAPIError.requestFailed 42 | } 43 | 44 | // MARK: Handle Error 45 | // check the response code 46 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 47 | if statusCode != 200 { 48 | // error getting token 49 | throw JamfAPIError.http(statusCode) 50 | } 51 | 52 | // print(String(data: data, encoding: .utf8) ?? "no data") 53 | 54 | // MARK: Parse JSON Data 55 | let decoder = JSONDecoder() 56 | 57 | // set date decoding to match Jamf's date format 58 | let dateFormatter = DateFormatter() 59 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 60 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 61 | 62 | do { 63 | let result = try decoder.decode(CategoryResults.self, from: data) 64 | 65 | return result.results 66 | 67 | // handle decoding errors 68 | // see DecodingError documentation for details 69 | } catch let DecodingError.dataCorrupted(context) { 70 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 71 | } catch let DecodingError.keyNotFound(key, context) { 72 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 73 | } catch let DecodingError.valueNotFound(value, context) { 74 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 75 | } catch let DecodingError.typeMismatch(type, context) { 76 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 77 | } catch { 78 | print("error: ", error) 79 | } 80 | 81 | throw JamfAPIError.decode 82 | } 83 | } 84 | 85 | struct CategoryResults: Codable { 86 | var totalCount: Int 87 | var results: [Category] 88 | } 89 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/CLI Tool/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: Codable { 11 | 12 | struct General: Codable { 13 | var name: String 14 | var assetTag: String? 15 | var lastEnrolledDate: Date 16 | var userApprovedMdm: Bool 17 | } 18 | 19 | struct Hardware: Codable { 20 | var model: String 21 | var modelIdentifier: String 22 | var serialNumber: String 23 | var appleSilicon: Bool 24 | } 25 | 26 | struct OperatingSystem: Codable { 27 | var name: String 28 | var version: String 29 | var build: String 30 | } 31 | 32 | var id: String 33 | var general: General 34 | var hardware: Hardware 35 | var operatingSystem: OperatingSystem 36 | 37 | /** Gets a list of all computers from the Jamf Pro Server */ 38 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Computer] { 39 | // MARK: Prepare Request 40 | // assemble the URL for the Jamf API 41 | guard var components = URLComponents(string: server) 42 | else { 43 | throw JamfAPIError.badURL 44 | } 45 | components.path="/api/v1/computers-inventory" 46 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 47 | URLQueryItem(name: "section", value: "HARDWARE"), 48 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 49 | URLQueryItem(name: "sort", value: "id:asc") ] 50 | 51 | guard let url = components.url 52 | else { 53 | throw JamfAPIError.badURL 54 | } 55 | 56 | // print("Request URL: \(url.absoluteString)") 57 | 58 | // MARK: Send Request and get Data 59 | // create the request 60 | var request = URLRequest(url: url) 61 | request.httpMethod = "GET" 62 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 63 | 64 | // send request and get data 65 | guard let (data, response) = try? await URLSession.shared.data(for: request) 66 | else { 67 | throw JamfAPIError.requestFailed 68 | } 69 | 70 | // MARK: Handle Error 71 | // check the response code 72 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 73 | if statusCode != 200 { 74 | // error getting token 75 | throw JamfAPIError.http(statusCode) 76 | } 77 | 78 | // print(String(data: data, encoding: .utf8) ?? "no data") 79 | 80 | // MARK: Parse JSON Data 81 | let decoder = JSONDecoder() 82 | 83 | // set date decoding to match Jamf's date format 84 | let dateFormatter = DateFormatter() 85 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 86 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 87 | 88 | do { 89 | let result = try decoder.decode(ComputerResults.self, from: data) 90 | 91 | return result.results 92 | 93 | // handle decoding errors 94 | // see DecodingError documentation for details 95 | } catch let DecodingError.dataCorrupted(context) { 96 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 97 | } catch let DecodingError.keyNotFound(key, context) { 98 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 99 | } catch let DecodingError.valueNotFound(value, context) { 100 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 101 | } catch let DecodingError.typeMismatch(type, context) { 102 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 103 | } catch { 104 | print("error: ", error) 105 | } 106 | 107 | throw JamfAPIError.decode 108 | } 109 | } 110 | 111 | struct ComputerResults: Codable { 112 | var totalCount: Int 113 | var results: [Computer] 114 | } 115 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/CLI Tool/JamfAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAPIError.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum JamfAPIError: Error { 11 | case requestFailed 12 | case http(Int) 13 | case authentication 14 | case decode 15 | case encode 16 | case badURL 17 | case noCredentials 18 | } 19 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/CLI Tool/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | 14 | 15 | /** Get an authentication Token from the server */ 16 | static func get(server: String, username: String, password: String) async throws -> JamfAuthToken { 17 | 18 | // MARK: Prepare Request 19 | // encode user name and password 20 | let base64 = "\(username):\(password)" 21 | .data(using: String.Encoding.utf8)! 22 | .base64EncodedString() 23 | 24 | // assemble the URL for the Jamf API 25 | guard var components = URLComponents(string: server) else { 26 | throw JamfAPIError.badURL 27 | } 28 | components.path="/api/v1/auth/token" 29 | guard let url = components.url else { 30 | throw JamfAPIError.badURL 31 | } 32 | 33 | // MARK: Send Request and get Data 34 | 35 | // create the request 36 | var authRequest = URLRequest(url: url) 37 | authRequest.httpMethod = "POST" 38 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 39 | 40 | // send request and get data 41 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 42 | else { 43 | throw JamfAPIError.requestFailed 44 | } 45 | 46 | // MARK: Handle Errors 47 | 48 | // check the response code 49 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 50 | if authStatusCode != 200 { 51 | throw JamfAPIError.http(authStatusCode) 52 | } 53 | 54 | // print(String(data: data, encoding: .utf8) ?? "no data") 55 | 56 | // MARK: Parse JSON returned 57 | let decoder = JSONDecoder() 58 | 59 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 60 | else { 61 | throw JamfAPIError.decode 62 | } 63 | 64 | return auth 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | static func main() async { 13 | let server = "https://jamf.example.com" 14 | let username = "api_" 15 | 16 | // TODO: get this from Keychain 17 | let password = "secret_password" 18 | 19 | // errors thrown in the do block will be processed in the catch block 20 | do { 21 | // get authentication token 22 | let auth = try await JamfAuthToken.get(server: server, username: username, password: password) 23 | 24 | // get categories 25 | let categories = try await Category.getAll(server: server, auth: auth) 26 | 27 | for category in categories { 28 | print(category.id, 29 | category.name, 30 | category.priority) 31 | } 32 | 33 | // catch the errors 34 | } catch JamfAPIError.badURL { 35 | print("could not create API URL") 36 | exit(11) 37 | } catch JamfAPIError.requestFailed { 38 | print("API request failed") 39 | exit(12) 40 | } catch JamfAPIError.http(let statusCode) { 41 | print("HTTP error: \(statusCode)") 42 | exit(13) 43 | } catch JamfAPIError.decode { 44 | print("could not decode JSON") 45 | exit(14) 46 | } catch { 47 | print("other error: \(error)") 48 | exit(99) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C6873FB528D8A37000FA893B /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB428D8A37000FA893B /* Category.swift */; }; 11 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 12 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB828D8B13700FA893B /* JamfAPIError.swift */; }; 13 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 14 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = /usr/share/man/man1/; 22 | dstSubfolderSpec = 0; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 1; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | C6873FB428D8A37000FA893B /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 31 | C6873FB628D8A4D100FA893B /* Computer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Computer.swift; sourceTree = ""; }; 32 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAPIError.swift; sourceTree = ""; }; 33 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 35 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; tabWidth = 2; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | C6F53D6C28C6295600E0B11F = { 50 | isa = PBXGroup; 51 | children = ( 52 | C6F53D7728C6295600E0B11F /* CLI Tool */, 53 | C6F53D7628C6295600E0B11F /* Products */, 54 | ); 55 | indentWidth = 2; 56 | sourceTree = ""; 57 | tabWidth = 2; 58 | }; 59 | C6F53D7628C6295600E0B11F /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C6F53D7528C6295600E0B11F /* jamf_list */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 71 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */, 72 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 73 | C6873FB628D8A4D100FA893B /* Computer.swift */, 74 | C6873FB428D8A37000FA893B /* Category.swift */, 75 | ); 76 | path = "CLI Tool"; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXNativeTarget section */ 82 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 83 | isa = PBXNativeTarget; 84 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 85 | buildPhases = ( 86 | C6F53D7128C6295600E0B11F /* Sources */, 87 | C6F53D7228C6295600E0B11F /* Frameworks */, 88 | C6F53D7328C6295600E0B11F /* CopyFiles */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = jamf_list; 95 | productName = SwiftJamfAPI; 96 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 97 | productType = "com.apple.product-type.tool"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | C6F53D6D28C6295600E0B11F /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | BuildIndependentTargetsInParallel = 1; 106 | LastSwiftUpdateCheck = 1340; 107 | LastUpgradeCheck = 1400; 108 | TargetAttributes = { 109 | C6F53D7428C6295600E0B11F = { 110 | CreatedOnToolsVersion = 13.4.1; 111 | LastSwiftMigration = 1340; 112 | }; 113 | }; 114 | }; 115 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 116 | compatibilityVersion = "Xcode 13.0"; 117 | developmentRegion = en; 118 | hasScannedForEncodings = 0; 119 | knownRegions = ( 120 | en, 121 | Base, 122 | ); 123 | mainGroup = C6F53D6C28C6295600E0B11F; 124 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | C6F53D7428C6295600E0B11F /* jamf_list */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | C6F53D7128C6295600E0B11F /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */, 139 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 140 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */, 141 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 142 | C6873FB528D8A37000FA893B /* Category.swift in Sources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXSourcesBuildPhase section */ 147 | 148 | /* Begin XCBuildConfiguration section */ 149 | C6F53D7A28C6295600E0B11F /* Debug */ = { 150 | isa = XCBuildConfiguration; 151 | buildSettings = { 152 | ALWAYS_SEARCH_USER_PATHS = NO; 153 | CLANG_ANALYZER_NONNULL = YES; 154 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 155 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 156 | CLANG_ENABLE_MODULES = YES; 157 | CLANG_ENABLE_OBJC_ARC = YES; 158 | CLANG_ENABLE_OBJC_WEAK = YES; 159 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 160 | CLANG_WARN_BOOL_CONVERSION = YES; 161 | CLANG_WARN_COMMA = YES; 162 | CLANG_WARN_CONSTANT_CONVERSION = YES; 163 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 164 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 165 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 166 | CLANG_WARN_EMPTY_BODY = YES; 167 | CLANG_WARN_ENUM_CONVERSION = YES; 168 | CLANG_WARN_INFINITE_RECURSION = YES; 169 | CLANG_WARN_INT_CONVERSION = YES; 170 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 171 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 172 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 173 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 174 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 175 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 176 | CLANG_WARN_STRICT_PROTOTYPES = YES; 177 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 178 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 179 | CLANG_WARN_UNREACHABLE_CODE = YES; 180 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 181 | COPY_PHASE_STRIP = NO; 182 | DEAD_CODE_STRIPPING = YES; 183 | DEBUG_INFORMATION_FORMAT = dwarf; 184 | ENABLE_STRICT_OBJC_MSGSEND = YES; 185 | ENABLE_TESTABILITY = YES; 186 | GCC_C_LANGUAGE_STANDARD = gnu11; 187 | GCC_DYNAMIC_NO_PIC = NO; 188 | GCC_NO_COMMON_BLOCKS = YES; 189 | GCC_OPTIMIZATION_LEVEL = 0; 190 | GCC_PREPROCESSOR_DEFINITIONS = ( 191 | "DEBUG=1", 192 | "$(inherited)", 193 | ); 194 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 195 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 196 | GCC_WARN_UNDECLARED_SELECTOR = YES; 197 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 198 | GCC_WARN_UNUSED_FUNCTION = YES; 199 | GCC_WARN_UNUSED_VARIABLE = YES; 200 | MACOSX_DEPLOYMENT_TARGET = 12.0; 201 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 202 | MTL_FAST_MATH = YES; 203 | ONLY_ACTIVE_ARCH = YES; 204 | SDKROOT = macosx; 205 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 206 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 207 | }; 208 | name = Debug; 209 | }; 210 | C6F53D7B28C6295600E0B11F /* Release */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 217 | CLANG_ENABLE_MODULES = YES; 218 | CLANG_ENABLE_OBJC_ARC = YES; 219 | CLANG_ENABLE_OBJC_WEAK = YES; 220 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_COMMA = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 226 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 227 | CLANG_WARN_EMPTY_BODY = YES; 228 | CLANG_WARN_ENUM_CONVERSION = YES; 229 | CLANG_WARN_INFINITE_RECURSION = YES; 230 | CLANG_WARN_INT_CONVERSION = YES; 231 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 233 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 234 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 235 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 236 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 237 | CLANG_WARN_STRICT_PROTOTYPES = YES; 238 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 239 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 240 | CLANG_WARN_UNREACHABLE_CODE = YES; 241 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 242 | COPY_PHASE_STRIP = NO; 243 | DEAD_CODE_STRIPPING = YES; 244 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 245 | ENABLE_NS_ASSERTIONS = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu11; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | MACOSX_DEPLOYMENT_TARGET = 12.0; 256 | MTL_ENABLE_DEBUG_INFO = NO; 257 | MTL_FAST_MATH = YES; 258 | SDKROOT = macosx; 259 | SWIFT_COMPILATION_MODE = wholemodule; 260 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 261 | }; 262 | name = Release; 263 | }; 264 | C6F53D7D28C6295600E0B11F /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | CLANG_ENABLE_MODULES = YES; 268 | CODE_SIGN_IDENTITY = "-"; 269 | CODE_SIGN_STYLE = Automatic; 270 | DEAD_CODE_STRIPPING = YES; 271 | DEVELOPMENT_TEAM = JME5BW3F3R; 272 | ENABLE_HARDENED_RUNTIME = YES; 273 | LD_RUNPATH_SEARCH_PATHS = ( 274 | "$(inherited)", 275 | "@executable_path/../Frameworks", 276 | "@loader_path/../Frameworks", 277 | ); 278 | PRODUCT_NAME = "$(TARGET_NAME)"; 279 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 280 | SWIFT_VERSION = 5.0; 281 | }; 282 | name = Debug; 283 | }; 284 | C6F53D7E28C6295600E0B11F /* Release */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | CLANG_ENABLE_MODULES = YES; 288 | CODE_SIGN_IDENTITY = "-"; 289 | CODE_SIGN_STYLE = Automatic; 290 | DEAD_CODE_STRIPPING = YES; 291 | DEVELOPMENT_TEAM = JME5BW3F3R; 292 | ENABLE_HARDENED_RUNTIME = YES; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/../Frameworks", 296 | "@loader_path/../Frameworks", 297 | ); 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Release; 302 | }; 303 | /* End XCBuildConfiguration section */ 304 | 305 | /* Begin XCConfigurationList section */ 306 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | C6F53D7A28C6295600E0B11F /* Debug */, 310 | C6F53D7B28C6295600E0B11F /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | C6F53D7D28C6295600E0B11F /* Debug */, 319 | C6F53D7E28C6295600E0B11F /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | /* End XCConfigurationList section */ 325 | }; 326 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 327 | } 328 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3A/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftJamfAPI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | jamf_list.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | static func main() async { 13 | let server = "https://jamf.example.com" 14 | let username = "api_user" 15 | 16 | // TODO: get this from Keychain 17 | let password = "secret_password" 18 | 19 | // errors thrown in the do block will be processed in the catch block 20 | do { 21 | // get authentication token 22 | let auth = try await JamfAuthToken.get(server: server, username: username, password: password) 23 | 24 | // get categories 25 | let categories = try await Category.getAll(server: server, auth: auth) 26 | 27 | for category in categories { 28 | print(category.id, 29 | category.name, 30 | category.priority) 31 | } 32 | 33 | print() 34 | // get computers 35 | let computers = try await Computer.getAll(server: server, auth: auth) 36 | 37 | for computer in computers { 38 | print(computer.id, 39 | computer.general.name) 40 | } 41 | 42 | print() 43 | // get scripts 44 | let scripts = try await Script.getAll(server: server, auth: auth) 45 | 46 | for script in scripts { 47 | print(script.name) 48 | 49 | // count the lines and prints that 50 | print("\(script.scriptContents.split(separator: "\n").count) lines of code") 51 | } 52 | 53 | // catch the errors 54 | } catch JamfAPIError.badURL { 55 | print("could not create API URL") 56 | exit(11) 57 | } catch JamfAPIError.requestFailed { 58 | print("API request failed") 59 | exit(12) 60 | } catch JamfAPIError.http(let statusCode) { 61 | print("HTTP error: \(statusCode)") 62 | exit(13) 63 | } catch JamfAPIError.decode { 64 | print("could not decode JSON") 65 | exit(14) 66 | } catch { 67 | print("other error: \(error)") 68 | exit(99) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: JamfObject { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | 15 | static let getAllEndpoint = "/api/v1/categories" 16 | } 17 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: JamfObject { 11 | 12 | struct General: Codable { 13 | var name: String 14 | var assetTag: String? 15 | var lastEnrolledDate: Date 16 | var userApprovedMdm: Bool 17 | } 18 | 19 | struct Hardware: Codable { 20 | var model: String 21 | var modelIdentifier: String 22 | var serialNumber: String 23 | var appleSilicon: Bool 24 | } 25 | 26 | struct OperatingSystem: Codable { 27 | var name: String 28 | var version: String 29 | var build: String 30 | } 31 | 32 | var id: String 33 | var general: General 34 | var hardware: Hardware 35 | var operatingSystem: OperatingSystem 36 | 37 | // MARK: JamfObject implementation 38 | static let getAllEndpoint = "/api/v1/computers-inventory" 39 | 40 | // override getAllURLComponents to add query items 41 | static func getAllURLComponents(server: String) throws -> URLComponents { 42 | guard var components = URLComponents(string: server) 43 | else { 44 | throw JamfAPIError.badURL 45 | } 46 | components.path = self.getAllEndpoint 47 | 48 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 49 | URLQueryItem(name: "section", value: "HARDWARE"), 50 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 51 | URLQueryItem(name: "sort", value: "id:asc") ] 52 | return components 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/JamfAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAPIError.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum JamfAPIError: Error { 11 | case requestFailed 12 | case http(Int) 13 | case authentication 14 | case decode 15 | case encode 16 | case badURL 17 | case noCredentials 18 | } 19 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | 14 | 15 | /** Get an authentication Token from the server */ 16 | static func get(server: String, username: String, password: String) async throws -> JamfAuthToken { 17 | 18 | // MARK: Prepare Request 19 | // encode user name and password 20 | let base64 = "\(username):\(password)" 21 | .data(using: String.Encoding.utf8)! 22 | .base64EncodedString() 23 | 24 | // assemble the URL for the Jamf API 25 | guard var components = URLComponents(string: server) else { 26 | throw JamfAPIError.badURL 27 | } 28 | components.path="/api/v1/auth/token" 29 | guard let url = components.url else { 30 | throw JamfAPIError.badURL 31 | } 32 | 33 | // MARK: Send Request and get Data 34 | 35 | // create the request 36 | var authRequest = URLRequest(url: url) 37 | authRequest.httpMethod = "POST" 38 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 39 | 40 | // send request and get data 41 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 42 | else { 43 | throw JamfAPIError.requestFailed 44 | } 45 | 46 | // MARK: Handle Errors 47 | 48 | // check the response code 49 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 50 | if authStatusCode != 200 { 51 | throw JamfAPIError.http(authStatusCode) 52 | } 53 | 54 | // print(String(data: data, encoding: .utf8) ?? "no data") 55 | 56 | // MARK: Parse JSON returned 57 | let decoder = JSONDecoder() 58 | 59 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 60 | else { 61 | throw JamfAPIError.decode 62 | } 63 | 64 | return auth 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/JamfObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfObject.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-26. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JamfObject: Codable { 11 | var id: String { get set } 12 | 13 | static var getAllEndpoint: String { get } 14 | 15 | /** Build the URL for the request to fetch all objects */ 16 | static func getAllURLComponents(server: String) throws -> URLComponents 17 | 18 | /** Gets a list of all objects from the Jamf Pro Server */ 19 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] 20 | } 21 | 22 | extension JamfObject { 23 | 24 | /** build the URL for the request to fetch all categories */ 25 | static func getAllURLComponents(server: String) throws -> URLComponents { 26 | // assemble the URL for the Jamf API 27 | guard var components = URLComponents(string: server) 28 | else { 29 | throw JamfAPIError.badURL 30 | } 31 | components.path = self.getAllEndpoint 32 | 33 | return components 34 | } 35 | 36 | /** Gets a list of all categories from the Jamf Pro Server */ 37 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] { 38 | // MARK: Prepare Request 39 | let components = try getAllURLComponents(server: server) 40 | 41 | guard let url = components.url 42 | else { 43 | throw JamfAPIError.badURL 44 | } 45 | 46 | // print("Request URL: \(url.absoluteString)") 47 | 48 | // MARK: Send Request and get Data 49 | // create the request 50 | var request = URLRequest(url: url) 51 | request.httpMethod = "GET" 52 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 53 | 54 | // send request and get data 55 | guard let (data, response) = try? await URLSession.shared.data(for: request) 56 | else { 57 | throw JamfAPIError.requestFailed 58 | } 59 | 60 | // MARK: Handle Error 61 | // check the response code 62 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 63 | if statusCode != 200 { 64 | // error getting token 65 | throw JamfAPIError.http(statusCode) 66 | } 67 | 68 | // print(String(data: data, encoding: .utf8) ?? "no data") 69 | 70 | // MARK: Parse JSON Data 71 | let decoder = JSONDecoder() 72 | 73 | // set date decoding to match Jamf's date format 74 | let dateFormatter = DateFormatter() 75 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 76 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 77 | 78 | do { 79 | let result = try decoder.decode(JamfResults.self, from: data) 80 | 81 | return result.results 82 | 83 | // handle decoding errors 84 | // see DecodingError documentation for details 85 | } catch let DecodingError.dataCorrupted(context) { 86 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 87 | } catch let DecodingError.keyNotFound(key, context) { 88 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 89 | } catch let DecodingError.valueNotFound(value, context) { 90 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 91 | } catch let DecodingError.typeMismatch(type, context) { 92 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 93 | } catch { 94 | print("error: ", error) 95 | } 96 | 97 | throw JamfAPIError.decode 98 | } 99 | 100 | } 101 | 102 | struct JamfResults: Codable { 103 | var totalCount: Int 104 | var results: [T] 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Script: JamfObject { 11 | enum Priority: String, Codable { 12 | case before = "BEFORE" 13 | case after = "AFTER" 14 | } 15 | 16 | var id: String 17 | var name: String = "" 18 | var info: String = "" 19 | var notes: String = "" 20 | var priority: Priority = .after 21 | var parameter4: String = "" 22 | var parameter5: String = "" 23 | var parameter6: String = "" 24 | var parameter7: String = "" 25 | var parameter8: String = "" 26 | var parameter9: String = "" 27 | var parameter10: String = "" 28 | var parameter11: String = "" 29 | var osRequirements: String = "" 30 | var scriptContents: String = "" 31 | var categoryId: String = "-1" 32 | var categoryName: String = "" 33 | 34 | static var getAllEndpoint = "/api/v1/scripts" 35 | } 36 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA87290961D600B2E481 /* JamfObject.swift */; }; 11 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA89290AC9E900B2E481 /* Script.swift */; }; 12 | C6873FB528D8A37000FA893B /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB428D8A37000FA893B /* Category.swift */; }; 13 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 14 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB828D8B13700FA893B /* JamfAPIError.swift */; }; 15 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 16 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = /usr/share/man/man1/; 24 | dstSubfolderSpec = 0; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 1; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | C62CFA87290961D600B2E481 /* JamfObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfObject.swift; sourceTree = ""; }; 33 | C62CFA89290AC9E900B2E481 /* Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = ""; }; 34 | C6873FB428D8A37000FA893B /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 35 | C6873FB628D8A4D100FA893B /* Computer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Computer.swift; sourceTree = ""; }; 36 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAPIError.swift; sourceTree = ""; }; 37 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 39 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; tabWidth = 2; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | C62CFA8B290AD39A00B2E481 /* Model */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */, 57 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 58 | C62CFA87290961D600B2E481 /* JamfObject.swift */, 59 | C6873FB428D8A37000FA893B /* Category.swift */, 60 | C6873FB628D8A4D100FA893B /* Computer.swift */, 61 | C62CFA89290AC9E900B2E481 /* Script.swift */, 62 | ); 63 | name = Model; 64 | sourceTree = ""; 65 | }; 66 | C6F53D6C28C6295600E0B11F = { 67 | isa = PBXGroup; 68 | children = ( 69 | C6F53D7728C6295600E0B11F /* CLI Tool */, 70 | C62CFA8B290AD39A00B2E481 /* Model */, 71 | C6F53D7628C6295600E0B11F /* Products */, 72 | ); 73 | indentWidth = 2; 74 | sourceTree = ""; 75 | tabWidth = 2; 76 | }; 77 | C6F53D7628C6295600E0B11F /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | C6F53D7528C6295600E0B11F /* jamf_list */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 89 | ); 90 | path = "CLI Tool"; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 99 | buildPhases = ( 100 | C6F53D7128C6295600E0B11F /* Sources */, 101 | C6F53D7228C6295600E0B11F /* Frameworks */, 102 | C6F53D7328C6295600E0B11F /* CopyFiles */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = jamf_list; 109 | productName = SwiftJamfAPI; 110 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 111 | productType = "com.apple.product-type.tool"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | C6F53D6D28C6295600E0B11F /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | BuildIndependentTargetsInParallel = 1; 120 | LastSwiftUpdateCheck = 1340; 121 | LastUpgradeCheck = 1400; 122 | TargetAttributes = { 123 | C6F53D7428C6295600E0B11F = { 124 | CreatedOnToolsVersion = 13.4.1; 125 | LastSwiftMigration = 1340; 126 | }; 127 | }; 128 | }; 129 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 130 | compatibilityVersion = "Xcode 13.0"; 131 | developmentRegion = en; 132 | hasScannedForEncodings = 0; 133 | knownRegions = ( 134 | en, 135 | Base, 136 | ); 137 | mainGroup = C6F53D6C28C6295600E0B11F; 138 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 139 | projectDirPath = ""; 140 | projectRoot = ""; 141 | targets = ( 142 | C6F53D7428C6295600E0B11F /* jamf_list */, 143 | ); 144 | }; 145 | /* End PBXProject section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | C6F53D7128C6295600E0B11F /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */, 153 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */, 154 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 155 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */, 156 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 157 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */, 158 | C6873FB528D8A37000FA893B /* Category.swift in Sources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXSourcesBuildPhase section */ 163 | 164 | /* Begin XCBuildConfiguration section */ 165 | C6F53D7A28C6295600E0B11F /* Debug */ = { 166 | isa = XCBuildConfiguration; 167 | buildSettings = { 168 | ALWAYS_SEARCH_USER_PATHS = NO; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_ENABLE_OBJC_WEAK = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEAD_CODE_STRIPPING = YES; 199 | DEBUG_INFORMATION_FORMAT = dwarf; 200 | ENABLE_STRICT_OBJC_MSGSEND = YES; 201 | ENABLE_TESTABILITY = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu11; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_NO_COMMON_BLOCKS = YES; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_PREPROCESSOR_DEFINITIONS = ( 207 | "DEBUG=1", 208 | "$(inherited)", 209 | ); 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | MACOSX_DEPLOYMENT_TARGET = 12.0; 217 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 218 | MTL_FAST_MATH = YES; 219 | ONLY_ACTIVE_ARCH = YES; 220 | SDKROOT = macosx; 221 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 222 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 223 | }; 224 | name = Debug; 225 | }; 226 | C6F53D7B28C6295600E0B11F /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_ENABLE_OBJC_WEAK = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | COPY_PHASE_STRIP = NO; 259 | DEAD_CODE_STRIPPING = YES; 260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 261 | ENABLE_NS_ASSERTIONS = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu11; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | MACOSX_DEPLOYMENT_TARGET = 12.0; 272 | MTL_ENABLE_DEBUG_INFO = NO; 273 | MTL_FAST_MATH = YES; 274 | SDKROOT = macosx; 275 | SWIFT_COMPILATION_MODE = wholemodule; 276 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 277 | }; 278 | name = Release; 279 | }; 280 | C6F53D7D28C6295600E0B11F /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | CLANG_ENABLE_MODULES = YES; 284 | CODE_SIGN_IDENTITY = "-"; 285 | CODE_SIGN_STYLE = Automatic; 286 | DEAD_CODE_STRIPPING = YES; 287 | DEVELOPMENT_TEAM = JME5BW3F3R; 288 | ENABLE_HARDENED_RUNTIME = YES; 289 | LD_RUNPATH_SEARCH_PATHS = ( 290 | "$(inherited)", 291 | "@executable_path/../Frameworks", 292 | "@loader_path/../Frameworks", 293 | ); 294 | PRODUCT_NAME = "$(TARGET_NAME)"; 295 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 296 | SWIFT_VERSION = 5.0; 297 | }; 298 | name = Debug; 299 | }; 300 | C6F53D7E28C6295600E0B11F /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | CLANG_ENABLE_MODULES = YES; 304 | CODE_SIGN_IDENTITY = "-"; 305 | CODE_SIGN_STYLE = Automatic; 306 | DEAD_CODE_STRIPPING = YES; 307 | DEVELOPMENT_TEAM = JME5BW3F3R; 308 | ENABLE_HARDENED_RUNTIME = YES; 309 | LD_RUNPATH_SEARCH_PATHS = ( 310 | "$(inherited)", 311 | "@executable_path/../Frameworks", 312 | "@loader_path/../Frameworks", 313 | ); 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SWIFT_VERSION = 5.0; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | C6F53D7A28C6295600E0B11F /* Debug */, 326 | C6F53D7B28C6295600E0B11F /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | C6F53D7D28C6295600E0B11F /* Debug */, 335 | C6F53D7E28C6295600E0B11F /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-3B/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftJamfAPI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | jamf_list.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | enum JamfType: String { 13 | case computer 14 | case category 15 | case script 16 | } 17 | 18 | static func main() async { 19 | let args = CommandLine.arguments 20 | 21 | guard args.count > 3 22 | else { 23 | print("Not enough arguments") 24 | print("usage: jamf_list type server username [password]") 25 | exit(1) 26 | } 27 | 28 | // it is now safe to access `args[1...3]` 29 | guard let type = JamfType(rawValue: args[1]) 30 | else { 31 | print("\(args[1]) is not a known type") 32 | exit(2) 33 | } 34 | 35 | // connection info 36 | let server = args[2] 37 | let username = args[3] 38 | var password = "" 39 | 40 | // if there is a fourth argument, assign it to password 41 | if args.count > 4 { 42 | password = args[4] 43 | } else { 44 | // no password argument, get from keychain 45 | guard let passwordFromKeychain = try? Keychain.getPassword(service: server, account: username) 46 | else { 47 | print("Could not get password from keychain") 48 | exit(2) 49 | } 50 | password = passwordFromKeychain 51 | } 52 | 53 | // errors thrown in the do block will be processed in the catch block 54 | do { 55 | // get authentication token 56 | let auth = try await JamfAuthToken.get(server: server, username: username, password: password) 57 | 58 | switch type { 59 | case .category: 60 | let categories = try await Category.getAll(server: server, auth: auth) 61 | 62 | for category in categories { 63 | print(category.id, 64 | category.name, 65 | category.priority) 66 | } 67 | 68 | case .computer: 69 | // get computers 70 | let computers = try await Computer.getAll(server: server, auth: auth) 71 | 72 | for computer in computers { 73 | print(computer.id, 74 | computer.general.name) 75 | } 76 | 77 | case .script: 78 | //get scripts 79 | let scripts = try await Script.getAll(server:server, auth: auth) 80 | 81 | for script in scripts { 82 | print(script.id, 83 | script.name, 84 | "(\(script.scriptContents.split(separator: "\n").count) lines of code)") 85 | } 86 | } 87 | // catch the errors 88 | } catch JamfAPIError.badURL { 89 | print("could not create API URL") 90 | exit(11) 91 | } catch JamfAPIError.requestFailed { 92 | print("API request failed") 93 | exit(12) 94 | } catch JamfAPIError.http(let statusCode) { 95 | print("HTTP error: \(statusCode)") 96 | exit(13) 97 | } catch JamfAPIError.decode { 98 | print("could not decode JSON") 99 | exit(14) 100 | } catch { 101 | print("other error: \(error)") 102 | exit(99) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/CLI Tool/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // 4 | // Created by Armin Briegel on 2022-11-01. 5 | // 6 | 7 | import Foundation 8 | 9 | // built from the sample code here 10 | // https://developer.apple.com/documentation/security/keychain_services/keychain_items 11 | 12 | // I changed the class of the item to "internet password" 13 | // which makes it easier to manually create the item in keychain 14 | // and re-use items created when accessing the site in Safari 15 | 16 | // this reply here https://developer.apple.com/forums/thread/688612 17 | // recommended to use NSDictionary for the query dict to 18 | // avoid a lot of boiler plate type casting 19 | 20 | struct Keychain { 21 | enum KeychainError: Error { 22 | case noPassword // no matching item found 23 | case duplicateItem // found more than on matching item 24 | case unexpectedPasswordData // could not read item data 25 | case unhandledError(OSStatus) // other errors 26 | } 27 | 28 | static func save(password: String, service: String, account: String, label: String? = nil) throws { 29 | // if label is unset, use service as the label 30 | // this matches what keychain app does when creating a new item 31 | let safeLabel = label ?? service 32 | 33 | // when item already exists, use update instead 34 | if let _ = try? getPassword(service: service, account: account) { 35 | try update(password: password, service: service, account: account, label: label) 36 | return 37 | } 38 | 39 | let passwordData = Data(password.utf8) 40 | 41 | // create the Add Query Dict 42 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 43 | kSecAttrService: service, 44 | kSecAttrLabel: safeLabel, 45 | kSecAttrAccount: account, 46 | kSecValueData: passwordData] 47 | // add the item 48 | let status = SecItemAdd(query, nil) 49 | 50 | if status == errSecDuplicateItem { 51 | throw KeychainError.duplicateItem 52 | } 53 | 54 | guard status == errSecSuccess else { 55 | throw KeychainError.unhandledError(status) 56 | } 57 | } 58 | 59 | static func update(password: String, service: String, account: String, label: String? = nil) throws { 60 | let passwordData = Data(password.utf8) 61 | 62 | // if label is unset, use service as the label 63 | let safeLabel = label ?? service 64 | 65 | // prepare a search query 66 | let query: NSDictionary = [kSecAttrService: service, 67 | kSecAttrAccount: account, 68 | kSecClass: kSecClassInternetPassword] 69 | 70 | // prepare new attributes 71 | let attributes: NSDictionary = [kSecValueData: passwordData, 72 | kSecAttrLabel: safeLabel] 73 | // execute the update 74 | let status = SecItemUpdate(query, attributes) 75 | 76 | guard status != errSecItemNotFound else { 77 | throw KeychainError.noPassword 78 | } 79 | 80 | guard status == errSecSuccess else { 81 | throw KeychainError.unhandledError(status) 82 | } 83 | } 84 | 85 | static func getPassword(service: String, account: String) throws -> String { 86 | // create the search query 87 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 88 | kSecAttrService: service, 89 | kSecAttrAccount: account, 90 | kSecMatchLimit: kSecMatchLimitOne, 91 | kSecReturnData: true] 92 | 93 | var item: CFTypeRef? 94 | let status = SecItemCopyMatching(query, &item) 95 | 96 | guard status != errSecItemNotFound 97 | else { 98 | throw KeychainError.noPassword 99 | } 100 | 101 | guard status == errSecSuccess 102 | else { 103 | throw KeychainError.unhandledError(status) 104 | } 105 | 106 | // extract the result 107 | guard let passwordData = item as? Data, 108 | let password = String(data: passwordData, encoding: String.Encoding.utf8) 109 | else { 110 | throw KeychainError.unexpectedPasswordData 111 | } 112 | 113 | return password 114 | } 115 | 116 | static func delete(service: String, account: String) throws { 117 | // prepare the query dict 118 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 119 | kSecAttrService: service, 120 | kSecAttrAccount: account] 121 | 122 | // delete the item 123 | let status = SecItemDelete(query) 124 | 125 | guard status != errSecItemNotFound 126 | else { 127 | throw KeychainError.noPassword 128 | } 129 | 130 | guard status == errSecSuccess else { 131 | throw KeychainError.unhandledError(status) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: JamfObject { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | 15 | static let getAllEndpoint = "/api/v1/categories" 16 | } 17 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: JamfObject { 11 | 12 | struct General: Codable { 13 | var name: String 14 | var assetTag: String? 15 | var lastEnrolledDate: Date 16 | var userApprovedMdm: Bool 17 | } 18 | 19 | struct Hardware: Codable { 20 | var model: String 21 | var modelIdentifier: String 22 | var serialNumber: String 23 | var appleSilicon: Bool 24 | } 25 | 26 | struct OperatingSystem: Codable { 27 | var name: String 28 | var version: String 29 | var build: String 30 | } 31 | 32 | var id: String 33 | var general: General 34 | var hardware: Hardware 35 | var operatingSystem: OperatingSystem 36 | 37 | // MARK: JamfObject implementation 38 | static let getAllEndpoint = "/api/v1/computers-inventory" 39 | 40 | // override getAllURLComponents to add query items 41 | static func getAllURLComponents(server: String) throws -> URLComponents { 42 | guard var components = URLComponents(string: server) 43 | else { 44 | throw JamfAPIError.badURL 45 | } 46 | components.path = self.getAllEndpoint 47 | 48 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 49 | URLQueryItem(name: "section", value: "HARDWARE"), 50 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 51 | URLQueryItem(name: "sort", value: "id:asc") ] 52 | return components 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/JamfAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAPIError.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum JamfAPIError: Error { 11 | case requestFailed 12 | case http(Int) 13 | case authentication 14 | case decode 15 | case encode 16 | case badURL 17 | case noCredentials 18 | } 19 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | 14 | 15 | /** Get an authentication Token from the server */ 16 | static func get(server: String, username: String, password: String) async throws -> JamfAuthToken { 17 | 18 | // MARK: Prepare Request 19 | // encode user name and password 20 | let base64 = "\(username):\(password)" 21 | .data(using: String.Encoding.utf8)! 22 | .base64EncodedString() 23 | 24 | // assemble the URL for the Jamf API 25 | guard var components = URLComponents(string: server) else { 26 | throw JamfAPIError.badURL 27 | } 28 | components.path="/api/v1/auth/token" 29 | guard let url = components.url else { 30 | throw JamfAPIError.badURL 31 | } 32 | 33 | // MARK: Send Request and get Data 34 | 35 | // create the request 36 | var authRequest = URLRequest(url: url) 37 | authRequest.httpMethod = "POST" 38 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 39 | 40 | // send request and get data 41 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 42 | else { 43 | throw JamfAPIError.requestFailed 44 | } 45 | 46 | // MARK: Handle Errors 47 | 48 | // check the response code 49 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 50 | if authStatusCode != 200 { 51 | throw JamfAPIError.http(authStatusCode) 52 | } 53 | 54 | // print(String(data: data, encoding: .utf8) ?? "no data") 55 | 56 | // MARK: Parse JSON returned 57 | let decoder = JSONDecoder() 58 | 59 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 60 | else { 61 | throw JamfAPIError.decode 62 | } 63 | 64 | return auth 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/JamfObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfObject.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-26. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JamfObject: Codable { 11 | var id: String { get set } 12 | 13 | static var getAllEndpoint: String { get } 14 | 15 | /** Build the URL for the request to fetch all objects */ 16 | static func getAllURLComponents(server: String) throws -> URLComponents 17 | 18 | /** Gets a list of all objects from the Jamf Pro Server */ 19 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] 20 | } 21 | 22 | extension JamfObject { 23 | 24 | /** build the URL for the request to fetch all categories */ 25 | static func getAllURLComponents(server: String) throws -> URLComponents { 26 | // assemble the URL for the Jamf API 27 | guard var components = URLComponents(string: server) 28 | else { 29 | throw JamfAPIError.badURL 30 | } 31 | components.path = self.getAllEndpoint 32 | 33 | return components 34 | } 35 | 36 | /** Gets a list of all categories from the Jamf Pro Server */ 37 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] { 38 | // MARK: Prepare Request 39 | let components = try getAllURLComponents(server: server) 40 | 41 | guard let url = components.url 42 | else { 43 | throw JamfAPIError.badURL 44 | } 45 | 46 | // print("Request URL: \(url.absoluteString)") 47 | 48 | // MARK: Send Request and get Data 49 | // create the request 50 | var request = URLRequest(url: url) 51 | request.httpMethod = "GET" 52 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 53 | 54 | // send request and get data 55 | guard let (data, response) = try? await URLSession.shared.data(for: request) 56 | else { 57 | throw JamfAPIError.requestFailed 58 | } 59 | 60 | // MARK: Handle Error 61 | // check the response code 62 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 63 | if statusCode != 200 { 64 | // error getting token 65 | throw JamfAPIError.http(statusCode) 66 | } 67 | 68 | // print(String(data: data, encoding: .utf8) ?? "no data") 69 | 70 | // MARK: Parse JSON Data 71 | let decoder = JSONDecoder() 72 | 73 | // set date decoding to match Jamf's date format 74 | let dateFormatter = DateFormatter() 75 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 76 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 77 | 78 | do { 79 | let result = try decoder.decode(JamfResults.self, from: data) 80 | 81 | return result.results 82 | 83 | // handle decoding errors 84 | // see DecodingError documentation for details 85 | } catch let DecodingError.dataCorrupted(context) { 86 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 87 | } catch let DecodingError.keyNotFound(key, context) { 88 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 89 | } catch let DecodingError.valueNotFound(value, context) { 90 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 91 | } catch let DecodingError.typeMismatch(type, context) { 92 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 93 | } catch { 94 | print("error: ", error) 95 | } 96 | 97 | throw JamfAPIError.decode 98 | } 99 | 100 | } 101 | 102 | struct JamfResults: Codable { 103 | var totalCount: Int 104 | var results: [T] 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Script: JamfObject { 11 | enum Priority: String, Codable { 12 | case before = "BEFORE" 13 | case after = "AFTER" 14 | } 15 | 16 | var id: String 17 | var name: String = "" 18 | var info: String = "" 19 | var notes: String = "" 20 | var priority: Priority = .after 21 | var parameter4: String = "" 22 | var parameter5: String = "" 23 | var parameter6: String = "" 24 | var parameter7: String = "" 25 | var parameter8: String = "" 26 | var parameter9: String = "" 27 | var parameter10: String = "" 28 | var parameter11: String = "" 29 | var osRequirements: String = "" 30 | var scriptContents: String = "" 31 | var categoryId: String = "-1" 32 | var categoryName: String = "" 33 | 34 | static var getAllEndpoint = "/api/v1/scripts" 35 | } 36 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA87290961D600B2E481 /* JamfObject.swift */; }; 11 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA89290AC9E900B2E481 /* Script.swift */; }; 12 | C6873FB528D8A37000FA893B /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB428D8A37000FA893B /* Category.swift */; }; 13 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 14 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB828D8B13700FA893B /* JamfAPIError.swift */; }; 15 | C6C845312913CC1A00AF407A /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C845302913CC1A00AF407A /* Keychain.swift */; }; 16 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 17 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXCopyFilesBuildPhase section */ 21 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 22 | isa = PBXCopyFilesBuildPhase; 23 | buildActionMask = 2147483647; 24 | dstPath = /usr/share/man/man1/; 25 | dstSubfolderSpec = 0; 26 | files = ( 27 | ); 28 | runOnlyForDeploymentPostprocessing = 1; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | C62CFA87290961D600B2E481 /* JamfObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfObject.swift; sourceTree = ""; }; 34 | C62CFA89290AC9E900B2E481 /* Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = ""; }; 35 | C6873FB428D8A37000FA893B /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 36 | C6873FB628D8A4D100FA893B /* Computer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Computer.swift; sourceTree = ""; }; 37 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAPIError.swift; sourceTree = ""; }; 38 | C6C845302913CC1A00AF407A /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 39 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 41 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; tabWidth = 2; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | C62CFA8B290AD39A00B2E481 /* Model */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */, 59 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 60 | C62CFA87290961D600B2E481 /* JamfObject.swift */, 61 | C6873FB428D8A37000FA893B /* Category.swift */, 62 | C6873FB628D8A4D100FA893B /* Computer.swift */, 63 | C62CFA89290AC9E900B2E481 /* Script.swift */, 64 | ); 65 | name = Model; 66 | sourceTree = ""; 67 | }; 68 | C6F53D6C28C6295600E0B11F = { 69 | isa = PBXGroup; 70 | children = ( 71 | C6F53D7728C6295600E0B11F /* CLI Tool */, 72 | C62CFA8B290AD39A00B2E481 /* Model */, 73 | C6F53D7628C6295600E0B11F /* Products */, 74 | ); 75 | indentWidth = 2; 76 | sourceTree = ""; 77 | tabWidth = 2; 78 | }; 79 | C6F53D7628C6295600E0B11F /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | C6F53D7528C6295600E0B11F /* jamf_list */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 91 | C6C845302913CC1A00AF407A /* Keychain.swift */, 92 | ); 93 | path = "CLI Tool"; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 102 | buildPhases = ( 103 | C6F53D7128C6295600E0B11F /* Sources */, 104 | C6F53D7228C6295600E0B11F /* Frameworks */, 105 | C6F53D7328C6295600E0B11F /* CopyFiles */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = jamf_list; 112 | productName = SwiftJamfAPI; 113 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 114 | productType = "com.apple.product-type.tool"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | C6F53D6D28C6295600E0B11F /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | BuildIndependentTargetsInParallel = 1; 123 | LastSwiftUpdateCheck = 1340; 124 | LastUpgradeCheck = 1400; 125 | TargetAttributes = { 126 | C6F53D7428C6295600E0B11F = { 127 | CreatedOnToolsVersion = 13.4.1; 128 | LastSwiftMigration = 1340; 129 | }; 130 | }; 131 | }; 132 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 133 | compatibilityVersion = "Xcode 13.0"; 134 | developmentRegion = en; 135 | hasScannedForEncodings = 0; 136 | knownRegions = ( 137 | en, 138 | Base, 139 | ); 140 | mainGroup = C6F53D6C28C6295600E0B11F; 141 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 142 | projectDirPath = ""; 143 | projectRoot = ""; 144 | targets = ( 145 | C6F53D7428C6295600E0B11F /* jamf_list */, 146 | ); 147 | }; 148 | /* End PBXProject section */ 149 | 150 | /* Begin PBXSourcesBuildPhase section */ 151 | C6F53D7128C6295600E0B11F /* Sources */ = { 152 | isa = PBXSourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */, 156 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */, 157 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 158 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */, 159 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 160 | C6C845312913CC1A00AF407A /* Keychain.swift in Sources */, 161 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */, 162 | C6873FB528D8A37000FA893B /* Category.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | C6F53D7A28C6295600E0B11F /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_ANALYZER_NONNULL = YES; 174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_ENABLE_OBJC_WEAK = YES; 179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_COMMA = YES; 182 | CLANG_WARN_CONSTANT_CONVERSION = YES; 183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 196 | CLANG_WARN_STRICT_PROTOTYPES = YES; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEAD_CODE_STRIPPING = YES; 203 | DEBUG_INFORMATION_FORMAT = dwarf; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | ENABLE_TESTABILITY = YES; 206 | GCC_C_LANGUAGE_STANDARD = gnu11; 207 | GCC_DYNAMIC_NO_PIC = NO; 208 | GCC_NO_COMMON_BLOCKS = YES; 209 | GCC_OPTIMIZATION_LEVEL = 0; 210 | GCC_PREPROCESSOR_DEFINITIONS = ( 211 | "DEBUG=1", 212 | "$(inherited)", 213 | ); 214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 216 | GCC_WARN_UNDECLARED_SELECTOR = YES; 217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 218 | GCC_WARN_UNUSED_FUNCTION = YES; 219 | GCC_WARN_UNUSED_VARIABLE = YES; 220 | MACOSX_DEPLOYMENT_TARGET = 12.0; 221 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 222 | MTL_FAST_MATH = YES; 223 | ONLY_ACTIVE_ARCH = YES; 224 | SDKROOT = macosx; 225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 227 | }; 228 | name = Debug; 229 | }; 230 | C6F53D7B28C6295600E0B11F /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_ENABLE_OBJC_WEAK = YES; 240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_COMMA = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 247 | CLANG_WARN_EMPTY_BODY = YES; 248 | CLANG_WARN_ENUM_CONVERSION = YES; 249 | CLANG_WARN_INFINITE_RECURSION = YES; 250 | CLANG_WARN_INT_CONVERSION = YES; 251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 257 | CLANG_WARN_STRICT_PROTOTYPES = YES; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | COPY_PHASE_STRIP = NO; 263 | DEAD_CODE_STRIPPING = YES; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu11; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | MACOSX_DEPLOYMENT_TARGET = 12.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | MTL_FAST_MATH = YES; 278 | SDKROOT = macosx; 279 | SWIFT_COMPILATION_MODE = wholemodule; 280 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 281 | }; 282 | name = Release; 283 | }; 284 | C6F53D7D28C6295600E0B11F /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | CLANG_ENABLE_MODULES = YES; 288 | CODE_SIGN_IDENTITY = "-"; 289 | CODE_SIGN_STYLE = Automatic; 290 | DEAD_CODE_STRIPPING = YES; 291 | DEVELOPMENT_TEAM = JME5BW3F3R; 292 | ENABLE_HARDENED_RUNTIME = YES; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/../Frameworks", 296 | "@loader_path/../Frameworks", 297 | ); 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 300 | SWIFT_VERSION = 5.0; 301 | }; 302 | name = Debug; 303 | }; 304 | C6F53D7E28C6295600E0B11F /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | CLANG_ENABLE_MODULES = YES; 308 | CODE_SIGN_IDENTITY = "-"; 309 | CODE_SIGN_STYLE = Automatic; 310 | DEAD_CODE_STRIPPING = YES; 311 | DEVELOPMENT_TEAM = JME5BW3F3R; 312 | ENABLE_HARDENED_RUNTIME = YES; 313 | LD_RUNPATH_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "@executable_path/../Frameworks", 316 | "@loader_path/../Frameworks", 317 | ); 318 | PRODUCT_NAME = "$(TARGET_NAME)"; 319 | SWIFT_VERSION = 5.0; 320 | }; 321 | name = Release; 322 | }; 323 | /* End XCBuildConfiguration section */ 324 | 325 | /* Begin XCConfigurationList section */ 326 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | C6F53D7A28C6295600E0B11F /* Debug */, 330 | C6F53D7B28C6295600E0B11F /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | C6F53D7D28C6295600E0B11F /* Debug */, 339 | C6F53D7E28C6295600E0B11F /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | /* End XCConfigurationList section */ 345 | }; 346 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 347 | } 348 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/xcshareddata/xcschemes/jamf_list.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-4/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftJamfAPI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | jamf_list.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | C6F53D7428C6295600E0B11F 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | enum JamfType: String { 13 | case computer 14 | case category 15 | case script 16 | } 17 | 18 | static func main() async { 19 | let args = CommandLine.arguments 20 | 21 | guard args.count > 3 22 | else { 23 | print("Not enough arguments") 24 | print("usage: jamf_list type server username [password]") 25 | exit(1) 26 | } 27 | 28 | // it is now safe to access `args[1...3]` 29 | guard let type = JamfType(rawValue: args[1]) 30 | else { 31 | print("\(args[1]) is not a known type") 32 | exit(2) 33 | } 34 | 35 | // connection info 36 | let server = args[2] 37 | let username = args[3] 38 | var password = "" 39 | 40 | // if there is a fourth argument, assign it to password 41 | if args.count > 4 { 42 | password = args[4] 43 | } else { 44 | // no password argument, get from keychain 45 | guard let passwordFromKeychain = try? Keychain.getPassword(service: server, account: username) 46 | else { 47 | print("Could not get password from keychain") 48 | exit(2) 49 | } 50 | password = passwordFromKeychain 51 | } 52 | 53 | // errors thrown in the do block will be processed in the catch block 54 | do { 55 | // get authentication token 56 | let auth = try await JamfAuthToken.get(server: server, username: username, password: password) 57 | 58 | switch type { 59 | case .category: 60 | let categories = try await Category.getAll(server: server, auth: auth) 61 | 62 | for category in categories { 63 | print(category.id, 64 | category.name, 65 | category.priority) 66 | } 67 | 68 | case .computer: 69 | // get computers 70 | let computers = try await Computer.getAll(server: server, auth: auth) 71 | 72 | for computer in computers { 73 | print(computer.id, 74 | computer.general.name) 75 | } 76 | 77 | case .script: 78 | //get scripts 79 | let scripts = try await Script.getAll(server:server, auth: auth) 80 | 81 | for script in scripts { 82 | print(script.id, 83 | script.name, 84 | "(\(script.scriptContents.split(separator: "\n").count) lines of code)") 85 | } 86 | } 87 | // catch the errors 88 | } catch JamfAPIError.badURL { 89 | print("could not create API URL") 90 | exit(11) 91 | } catch JamfAPIError.requestFailed { 92 | print("API request failed") 93 | exit(12) 94 | } catch JamfAPIError.http(let statusCode) { 95 | print("HTTP error: \(statusCode)") 96 | exit(13) 97 | } catch JamfAPIError.decode { 98 | print("could not decode JSON") 99 | exit(14) 100 | } catch { 101 | print("other error: \(error)") 102 | exit(99) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/CLI Tool/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // 4 | // Created by Armin Briegel on 2022-11-01. 5 | // 6 | 7 | import Foundation 8 | 9 | // built from the sample code here 10 | // https://developer.apple.com/documentation/security/keychain_services/keychain_items 11 | 12 | // I changed the class of the item to "internet password" 13 | // which makes it easier to manually create the item in keychain 14 | // and re-use items created when accessing the site in Safari 15 | 16 | // this reply here https://developer.apple.com/forums/thread/688612 17 | // recommended to use NSDictionary for the query dict to 18 | // avoid a lot of boiler plate type casting 19 | 20 | struct Keychain { 21 | enum KeychainError: Error { 22 | case noPassword // no matching item found 23 | case duplicateItem // found more than on matching item 24 | case unexpectedPasswordData // could not read item data 25 | case unhandledError(OSStatus) // other errors 26 | } 27 | 28 | static func save(password: String, service: String, account: String, label: String? = nil) throws { 29 | // if label is unset, use service as the label 30 | // this matches what keychain app does when creating a new item 31 | let safeLabel = label ?? service 32 | 33 | // when item already exists, use update instead 34 | if let _ = try? getPassword(service: service, account: account) { 35 | try update(password: password, service: service, account: account, label: label) 36 | return 37 | } 38 | 39 | let passwordData = Data(password.utf8) 40 | 41 | // create the Add Query Dict 42 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 43 | kSecAttrService: service, 44 | kSecAttrLabel: safeLabel, 45 | kSecAttrAccount: account, 46 | kSecValueData: passwordData] 47 | // add the item 48 | let status = SecItemAdd(query, nil) 49 | 50 | if status == errSecDuplicateItem { 51 | throw KeychainError.duplicateItem 52 | } 53 | 54 | guard status == errSecSuccess else { 55 | throw KeychainError.unhandledError(status) 56 | } 57 | } 58 | 59 | static func update(password: String, service: String, account: String, label: String? = nil) throws { 60 | let passwordData = Data(password.utf8) 61 | 62 | // if label is unset, use service as the label 63 | let safeLabel = label ?? service 64 | 65 | // prepare a search query 66 | let query: NSDictionary = [kSecAttrService: service, 67 | kSecAttrAccount: account, 68 | kSecClass: kSecClassInternetPassword] 69 | 70 | // prepare new attributes 71 | let attributes: NSDictionary = [kSecValueData: passwordData, 72 | kSecAttrLabel: safeLabel] 73 | // execute the update 74 | let status = SecItemUpdate(query, attributes) 75 | 76 | guard status != errSecItemNotFound else { 77 | throw KeychainError.noPassword 78 | } 79 | 80 | guard status == errSecSuccess else { 81 | throw KeychainError.unhandledError(status) 82 | } 83 | } 84 | 85 | static func getPassword(service: String, account: String) throws -> String { 86 | // create the search query 87 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 88 | kSecAttrService: service, 89 | kSecAttrAccount: account, 90 | kSecMatchLimit: kSecMatchLimitOne, 91 | kSecReturnData: true] 92 | 93 | var item: CFTypeRef? 94 | let status = SecItemCopyMatching(query, &item) 95 | 96 | guard status != errSecItemNotFound 97 | else { 98 | throw KeychainError.noPassword 99 | } 100 | 101 | guard status == errSecSuccess 102 | else { 103 | throw KeychainError.unhandledError(status) 104 | } 105 | 106 | // extract the result 107 | guard let passwordData = item as? Data, 108 | let password = String(data: passwordData, encoding: String.Encoding.utf8) 109 | else { 110 | throw KeychainError.unexpectedPasswordData 111 | } 112 | 113 | return password 114 | } 115 | 116 | static func delete(service: String, account: String) throws { 117 | // prepare the query dict 118 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 119 | kSecAttrService: service, 120 | kSecAttrAccount: account] 121 | 122 | // delete the item 123 | let status = SecItemDelete(query) 124 | 125 | guard status != errSecItemNotFound 126 | else { 127 | throw KeychainError.noPassword 128 | } 129 | 130 | guard status == errSecSuccess else { 131 | throw KeychainError.unhandledError(status) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: JamfObject { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | 15 | static let getAllEndpoint = "/api/v1/categories" 16 | } 17 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/Computer-Sample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer-Sample.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-06. 6 | // 7 | 8 | import Foundation 9 | 10 | // provide sample data items for previews 11 | extension Computer { 12 | static var samples = [ 13 | Computer.sampleMacBookPro, 14 | Computer.sampleMacBookAir, 15 | Computer.sampleMacmini, 16 | Computer.sampleIMac, 17 | Computer.sampleMacStudio, 18 | Computer.sampleMacProGen1, 19 | Computer.sampleMacProGen2, 20 | Computer.sampleMacProGen3 21 | ] 22 | 23 | static var sampleMacBookPro: Computer { 24 | let general = General( 25 | name: "MacBook Pro", 26 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -86400.0), 27 | userApprovedMdm: true 28 | ) 29 | let hardware = Hardware( 30 | model: "MacBook Pro", 31 | modelIdentifier: "MacBookPro1,1", 32 | serialNumber: "SAMPLE111111", 33 | appleSilicon: false 34 | ) 35 | let operatingSystem = OperatingSystem( 36 | name: "macOS", 37 | version: "13.0", 38 | build: "22A100" 39 | ) 40 | 41 | let sample = Computer( 42 | id: "999991", 43 | general: general, 44 | hardware: hardware, 45 | operatingSystem: operatingSystem 46 | ) 47 | 48 | return sample 49 | } 50 | 51 | static var sampleMacBookAir: Computer { 52 | let general = General( 53 | name: "MacBook Air", 54 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -172800.0), 55 | userApprovedMdm: true 56 | ) 57 | let hardware = Hardware( 58 | model: "MacBook Air", 59 | modelIdentifier: "MacBookAir1,1", 60 | serialNumber: "SAMPLE222222", 61 | appleSilicon: true 62 | ) 63 | let operatingSystem = OperatingSystem( 64 | name: "macOS", 65 | version: "13.1", 66 | build: "22C65" 67 | ) 68 | 69 | let sample = Computer( 70 | id: "999992", 71 | general: general, 72 | hardware: hardware, 73 | operatingSystem: operatingSystem 74 | ) 75 | 76 | return sample 77 | } 78 | 79 | static var sampleMacmini: Computer { 80 | let general = General( 81 | name: "Mac mini", 82 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 4)), 83 | userApprovedMdm: true 84 | ) 85 | let hardware = Hardware( 86 | model: "Mac mini", 87 | modelIdentifier: "Macmini1,1", 88 | serialNumber: "SAMPLE333333", 89 | appleSilicon: false 90 | ) 91 | let operatingSystem = OperatingSystem( 92 | name: "macOS", 93 | version: "12.6.1", 94 | build: "21G200" 95 | ) 96 | 97 | let sample = Computer( 98 | id: "999993", 99 | general: general, 100 | hardware: hardware, 101 | operatingSystem: operatingSystem 102 | ) 103 | 104 | return sample 105 | } 106 | 107 | static var sampleIMac: Computer { 108 | let general = General( 109 | name: "iMac", 110 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 8)), 111 | userApprovedMdm: true 112 | ) 113 | let hardware = Hardware( 114 | model: "iMac", 115 | modelIdentifier: "iMac10,1", 116 | serialNumber: "SAMPLE444444", 117 | appleSilicon: false 118 | ) 119 | let operatingSystem = OperatingSystem( 120 | name: "macOS", 121 | version: "12.5", 122 | build: "21F200" 123 | ) 124 | 125 | let sample = Computer( 126 | id: "999994", 127 | general: general, 128 | hardware: hardware, 129 | operatingSystem: operatingSystem 130 | ) 131 | 132 | return sample 133 | } 134 | 135 | static var sampleMacStudio: Computer { 136 | let general = General( 137 | name: "Mac Studio", 138 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 12)), 139 | userApprovedMdm: true 140 | ) 141 | let hardware = Hardware( 142 | model: "Mac Studio", 143 | modelIdentifier: "Mac13,1", 144 | serialNumber: "SAMPLE555555", 145 | appleSilicon: false 146 | ) 147 | let operatingSystem = OperatingSystem( 148 | name: "macOS", 149 | version: "13.1", 150 | build: "22B200" 151 | ) 152 | 153 | let sample = Computer( 154 | id: "999995", 155 | general: general, 156 | hardware: hardware, 157 | operatingSystem: operatingSystem 158 | ) 159 | 160 | return sample 161 | } 162 | 163 | static var sampleMacProGen1: Computer { 164 | let general = General( 165 | name: "Mac Pro (2010)", 166 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 12)), 167 | userApprovedMdm: true 168 | ) 169 | let hardware = Hardware( 170 | model: "Mac Pro (2010)", 171 | modelIdentifier: "MacPro4,1", 172 | serialNumber: "SAMPLE666666", 173 | appleSilicon: false 174 | ) 175 | let operatingSystem = OperatingSystem( 176 | name: "macOS", 177 | version: "11.7", 178 | build: "20F200" 179 | ) 180 | 181 | let sample = Computer( 182 | id: "999996", 183 | general: general, 184 | hardware: hardware, 185 | operatingSystem: operatingSystem 186 | ) 187 | 188 | return sample 189 | } 190 | 191 | static var sampleMacProGen2: Computer { 192 | let general = General( 193 | name: "Mac Pro (2013)", 194 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 16)), 195 | userApprovedMdm: true 196 | ) 197 | let hardware = Hardware( 198 | model: "Mac Pro (2013)", 199 | modelIdentifier: "MacPro6,1", 200 | serialNumber: "SAMPLE7777777", 201 | appleSilicon: false 202 | ) 203 | let operatingSystem = OperatingSystem( 204 | name: "macOS", 205 | version: "11.7", 206 | build: "20F200" 207 | ) 208 | 209 | let sample = Computer( 210 | id: "999997", 211 | general: general, 212 | hardware: hardware, 213 | operatingSystem: operatingSystem 214 | ) 215 | 216 | return sample 217 | } 218 | 219 | static var sampleMacProGen3: Computer { 220 | let general = General( 221 | name: "Mac Pro (2019)", 222 | lastEnrolledDate: Date.init(timeIntervalSinceNow: -(86400.0 * 32)), 223 | userApprovedMdm: true 224 | ) 225 | let hardware = Hardware( 226 | model: "Mac Pro (2019)", 227 | modelIdentifier: "MacPro7,1", 228 | serialNumber: "SAMPLE888888", 229 | appleSilicon: false 230 | ) 231 | let operatingSystem = OperatingSystem( 232 | name: "macOS", 233 | version: "11.7", 234 | build: "20F200" 235 | ) 236 | 237 | let sample = Computer( 238 | id: "999998", 239 | general: general, 240 | hardware: hardware, 241 | operatingSystem: operatingSystem 242 | ) 243 | 244 | return sample 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: JamfObject { 11 | 12 | struct General: Codable { 13 | var name: String 14 | var assetTag: String? 15 | var lastEnrolledDate: Date 16 | var userApprovedMdm: Bool 17 | } 18 | 19 | struct Hardware: Codable { 20 | var model: String 21 | var modelIdentifier: String 22 | var serialNumber: String 23 | var appleSilicon: Bool 24 | } 25 | 26 | struct OperatingSystem: Codable { 27 | var name: String 28 | var version: String 29 | var build: String 30 | } 31 | 32 | var id: String 33 | var general: General 34 | var hardware: Hardware 35 | var operatingSystem: OperatingSystem 36 | 37 | // MARK: JamfObject implementation 38 | static let getAllEndpoint = "/api/v1/computers-inventory" 39 | 40 | // override getAllURLComponents to add query items 41 | static func getAllURLComponents(server: String) throws -> URLComponents { 42 | guard var components = URLComponents(string: server) 43 | else { 44 | throw JamfAPIError.badURL 45 | } 46 | components.path = self.getAllEndpoint 47 | 48 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 49 | URLQueryItem(name: "section", value: "HARDWARE"), 50 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 51 | URLQueryItem(name: "sort", value: "id:asc") ] 52 | return components 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAPIError.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum JamfAPIError: Error { 11 | case requestFailed 12 | case http(Int) 13 | case authentication 14 | case decode 15 | case encode 16 | case badURL 17 | case noCredentials 18 | } 19 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | 14 | 15 | /** Get an authentication Token from the server */ 16 | static func get(server: String, username: String, password: String) async throws -> JamfAuthToken { 17 | 18 | // MARK: Prepare Request 19 | // encode user name and password 20 | let base64 = "\(username):\(password)" 21 | .data(using: String.Encoding.utf8)! 22 | .base64EncodedString() 23 | 24 | // assemble the URL for the Jamf API 25 | guard var components = URLComponents(string: server) else { 26 | throw JamfAPIError.badURL 27 | } 28 | components.path="/api/v1/auth/token" 29 | guard let url = components.url else { 30 | throw JamfAPIError.badURL 31 | } 32 | 33 | // MARK: Send Request and get Data 34 | 35 | // create the request 36 | var authRequest = URLRequest(url: url) 37 | authRequest.httpMethod = "POST" 38 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 39 | 40 | // send request and get data 41 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 42 | else { 43 | throw JamfAPIError.requestFailed 44 | } 45 | 46 | // MARK: Handle Errors 47 | 48 | // check the response code 49 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 50 | if authStatusCode != 200 { 51 | throw JamfAPIError.http(authStatusCode) 52 | } 53 | 54 | // print(String(data: data, encoding: .utf8) ?? "no data") 55 | 56 | // MARK: Parse JSON returned 57 | let decoder = JSONDecoder() 58 | 59 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 60 | else { 61 | throw JamfAPIError.decode 62 | } 63 | 64 | return auth 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var computers = Computer.samples 12 | 13 | var body: some View { 14 | NavigationView { 15 | // first view is Master/List 16 | List(computers) { computer in 17 | NavigationLink( 18 | computer.general.name, 19 | destination: DetailView(computer: computer) 20 | ) 21 | } 22 | // placeholder for detail view 23 | Text("Select an item") 24 | .foregroundColor(.secondary) 25 | } 26 | } 27 | } 28 | 29 | struct ContentView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | ContentView() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/DetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailView.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-15. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DetailView: View { 11 | var computer: Computer 12 | 13 | var body: some View { 14 | VStack(alignment: .leading) { 15 | Text(computer.general.name) 16 | .font(.title) 17 | Divider() 18 | Text(computer.general.lastEnrolledDate.description) 19 | Text(computer.hardware.serialNumber) 20 | Text(computer.hardware.appleSilicon 21 | ? "Apple silicon" : "Intel") 22 | Text("macOS \(computer.operatingSystem.version)") 23 | Spacer() 24 | } 25 | .padding() 26 | } 27 | } 28 | 29 | struct DetailView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | DetailView(computer: Computer.sampleMacBookAir) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/JamfList.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/JamfListApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfListApp.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct JamfListApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfList/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/JamfObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfObject.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-26. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JamfObject: Codable, Identifiable { 11 | var id: String { get set } 12 | 13 | static var getAllEndpoint: String { get } 14 | 15 | /** Build the URL for the request to fetch all objects */ 16 | static func getAllURLComponents(server: String) throws -> URLComponents 17 | 18 | /** Gets a list of all objects from the Jamf Pro Server */ 19 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] 20 | } 21 | 22 | extension JamfObject { 23 | 24 | /** build the URL for the request to fetch all categories */ 25 | static func getAllURLComponents(server: String) throws -> URLComponents { 26 | // assemble the URL for the Jamf API 27 | guard var components = URLComponents(string: server) 28 | else { 29 | throw JamfAPIError.badURL 30 | } 31 | components.path = self.getAllEndpoint 32 | 33 | return components 34 | } 35 | 36 | /** Gets a list of all categories from the Jamf Pro Server */ 37 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] { 38 | // MARK: Prepare Request 39 | let components = try getAllURLComponents(server: server) 40 | 41 | guard let url = components.url 42 | else { 43 | throw JamfAPIError.badURL 44 | } 45 | 46 | // print("Request URL: \(url.absoluteString)") 47 | 48 | // MARK: Send Request and get Data 49 | // create the request 50 | var request = URLRequest(url: url) 51 | request.httpMethod = "GET" 52 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 53 | 54 | // send request and get data 55 | guard let (data, response) = try? await URLSession.shared.data(for: request) 56 | else { 57 | throw JamfAPIError.requestFailed 58 | } 59 | 60 | // MARK: Handle Error 61 | // check the response code 62 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 63 | if statusCode != 200 { 64 | // error getting token 65 | throw JamfAPIError.http(statusCode) 66 | } 67 | 68 | // print(String(data: data, encoding: .utf8) ?? "no data") 69 | 70 | // MARK: Parse JSON Data 71 | let decoder = JSONDecoder() 72 | 73 | // set date decoding to match Jamf's date format 74 | let dateFormatter = DateFormatter() 75 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 76 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 77 | 78 | do { 79 | let result = try decoder.decode(JamfResults.self, from: data) 80 | 81 | return result.results 82 | 83 | // handle decoding errors 84 | // see DecodingError documentation for details 85 | } catch let DecodingError.dataCorrupted(context) { 86 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 87 | } catch let DecodingError.keyNotFound(key, context) { 88 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 89 | } catch let DecodingError.valueNotFound(value, context) { 90 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 91 | } catch let DecodingError.typeMismatch(type, context) { 92 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 93 | } catch { 94 | print("error: ", error) 95 | } 96 | 97 | throw JamfAPIError.decode 98 | } 99 | 100 | } 101 | 102 | struct JamfResults: Codable { 103 | var totalCount: Int 104 | var results: [T] 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Script: JamfObject { 11 | enum Priority: String, Codable { 12 | case before = "BEFORE" 13 | case after = "AFTER" 14 | } 15 | 16 | var id: String 17 | var name: String = "" 18 | var info: String = "" 19 | var notes: String = "" 20 | var priority: Priority = .after 21 | var parameter4: String = "" 22 | var parameter5: String = "" 23 | var parameter6: String = "" 24 | var parameter7: String = "" 25 | var parameter8: String = "" 26 | var parameter9: String = "" 27 | var parameter10: String = "" 28 | var parameter11: String = "" 29 | var osRequirements: String = "" 30 | var scriptContents: String = "" 31 | var categoryId: String = "-1" 32 | var categoryName: String = "" 33 | 34 | static var getAllEndpoint = "/api/v1/scripts" 35 | } 36 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA87290961D600B2E481 /* JamfObject.swift */; }; 11 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA89290AC9E900B2E481 /* Script.swift */; }; 12 | C6873FB528D8A37000FA893B /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB428D8A37000FA893B /* Category.swift */; }; 13 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 14 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB828D8B13700FA893B /* JamfAPIError.swift */; }; 15 | C6A3346A294A227500FA22CD /* JamfListApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A33469294A227500FA22CD /* JamfListApp.swift */; }; 16 | C6A3346C294A227500FA22CD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3346B294A227500FA22CD /* ContentView.swift */; }; 17 | C6A3346E294A227600FA22CD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C6A3346D294A227600FA22CD /* Assets.xcassets */; }; 18 | C6A33472294A227600FA22CD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C6A33471294A227600FA22CD /* Preview Assets.xcassets */; }; 19 | C6A33477294B46BE00FA22CD /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A33476294B46BE00FA22CD /* DetailView.swift */; }; 20 | C6A33479294B4CB300FA22CD /* Computer-Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A33478294B4CB300FA22CD /* Computer-Sample.swift */; }; 21 | C6A3347A294B4D1000FA22CD /* Computer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB628D8A4D100FA893B /* Computer.swift */; }; 22 | C6A3347B294B4D2700FA22CD /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 23 | C6A3347C294B4D2700FA22CD /* JamfAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6873FB828D8B13700FA893B /* JamfAPIError.swift */; }; 24 | C6A3347D294B4D2700FA22CD /* JamfObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C62CFA87290961D600B2E481 /* JamfObject.swift */; }; 25 | C6C845312913CC1A00AF407A /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C845302913CC1A00AF407A /* Keychain.swift */; }; 26 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D7F28C629D300E0B11F /* JamfList.swift */; }; 27 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXCopyFilesBuildPhase section */ 31 | C6F53D7328C6295600E0B11F /* CopyFiles */ = { 32 | isa = PBXCopyFilesBuildPhase; 33 | buildActionMask = 2147483647; 34 | dstPath = /usr/share/man/man1/; 35 | dstSubfolderSpec = 0; 36 | files = ( 37 | ); 38 | runOnlyForDeploymentPostprocessing = 1; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | C62CFA87290961D600B2E481 /* JamfObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfObject.swift; sourceTree = ""; }; 44 | C62CFA89290AC9E900B2E481 /* Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = ""; }; 45 | C6873FB428D8A37000FA893B /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 46 | C6873FB628D8A4D100FA893B /* Computer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Computer.swift; sourceTree = ""; }; 47 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfAPIError.swift; sourceTree = ""; }; 48 | C6A33467294A227500FA22CD /* JamfList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JamfList.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | C6A33469294A227500FA22CD /* JamfListApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfListApp.swift; sourceTree = ""; }; 50 | C6A3346B294A227500FA22CD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 51 | C6A3346D294A227600FA22CD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | C6A3346F294A227600FA22CD /* JamfList.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JamfList.entitlements; sourceTree = ""; }; 53 | C6A33471294A227600FA22CD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 54 | C6A33476294B46BE00FA22CD /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; 55 | C6A33478294B4CB300FA22CD /* Computer-Sample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Computer-Sample.swift"; sourceTree = ""; }; 56 | C6C845302913CC1A00AF407A /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 57 | C6F53D7528C6295600E0B11F /* jamf_list */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jamf_list; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | C6F53D7F28C629D300E0B11F /* JamfList.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfList.swift; sourceTree = ""; tabWidth = 2; }; 59 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JamfAuthToken.swift; sourceTree = ""; tabWidth = 2; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | C6A33464294A227500FA22CD /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | C6F53D7228C6295600E0B11F /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | C62CFA8B290AD39A00B2E481 /* Model */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | C6873FB828D8B13700FA893B /* JamfAPIError.swift */, 84 | C6F53D8128C62CFF00E0B11F /* JamfAuthToken.swift */, 85 | C62CFA87290961D600B2E481 /* JamfObject.swift */, 86 | C6873FB428D8A37000FA893B /* Category.swift */, 87 | C6873FB628D8A4D100FA893B /* Computer.swift */, 88 | C6A33478294B4CB300FA22CD /* Computer-Sample.swift */, 89 | C62CFA89290AC9E900B2E481 /* Script.swift */, 90 | ); 91 | name = Model; 92 | sourceTree = ""; 93 | }; 94 | C6A33468294A227500FA22CD /* JamfList */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | C6A33469294A227500FA22CD /* JamfListApp.swift */, 98 | C6A3346B294A227500FA22CD /* ContentView.swift */, 99 | C6A33476294B46BE00FA22CD /* DetailView.swift */, 100 | C6A3346D294A227600FA22CD /* Assets.xcassets */, 101 | C6A3346F294A227600FA22CD /* JamfList.entitlements */, 102 | C6A33470294A227600FA22CD /* Preview Content */, 103 | ); 104 | path = JamfList; 105 | sourceTree = ""; 106 | }; 107 | C6A33470294A227600FA22CD /* Preview Content */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | C6A33471294A227600FA22CD /* Preview Assets.xcassets */, 111 | ); 112 | path = "Preview Content"; 113 | sourceTree = ""; 114 | }; 115 | C6F53D6C28C6295600E0B11F = { 116 | isa = PBXGroup; 117 | children = ( 118 | C6F53D7728C6295600E0B11F /* CLI Tool */, 119 | C62CFA8B290AD39A00B2E481 /* Model */, 120 | C6A33468294A227500FA22CD /* JamfList */, 121 | C6F53D7628C6295600E0B11F /* Products */, 122 | ); 123 | indentWidth = 2; 124 | sourceTree = ""; 125 | tabWidth = 2; 126 | }; 127 | C6F53D7628C6295600E0B11F /* Products */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | C6F53D7528C6295600E0B11F /* jamf_list */, 131 | C6A33467294A227500FA22CD /* JamfList.app */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | C6F53D7728C6295600E0B11F /* CLI Tool */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | C6F53D7F28C629D300E0B11F /* JamfList.swift */, 140 | C6C845302913CC1A00AF407A /* Keychain.swift */, 141 | ); 142 | path = "CLI Tool"; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | C6A33466294A227500FA22CD /* JamfList */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = C6A33475294A227600FA22CD /* Build configuration list for PBXNativeTarget "JamfList" */; 151 | buildPhases = ( 152 | C6A33463294A227500FA22CD /* Sources */, 153 | C6A33464294A227500FA22CD /* Frameworks */, 154 | C6A33465294A227500FA22CD /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = JamfList; 161 | productName = JamfList; 162 | productReference = C6A33467294A227500FA22CD /* JamfList.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | C6F53D7428C6295600E0B11F /* jamf_list */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */; 168 | buildPhases = ( 169 | C6F53D7128C6295600E0B11F /* Sources */, 170 | C6F53D7228C6295600E0B11F /* Frameworks */, 171 | C6F53D7328C6295600E0B11F /* CopyFiles */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = jamf_list; 178 | productName = SwiftJamfAPI; 179 | productReference = C6F53D7528C6295600E0B11F /* jamf_list */; 180 | productType = "com.apple.product-type.tool"; 181 | }; 182 | /* End PBXNativeTarget section */ 183 | 184 | /* Begin PBXProject section */ 185 | C6F53D6D28C6295600E0B11F /* Project object */ = { 186 | isa = PBXProject; 187 | attributes = { 188 | BuildIndependentTargetsInParallel = 1; 189 | LastSwiftUpdateCheck = 1420; 190 | LastUpgradeCheck = 1400; 191 | TargetAttributes = { 192 | C6A33466294A227500FA22CD = { 193 | CreatedOnToolsVersion = 14.2; 194 | }; 195 | C6F53D7428C6295600E0B11F = { 196 | CreatedOnToolsVersion = 13.4.1; 197 | LastSwiftMigration = 1340; 198 | }; 199 | }; 200 | }; 201 | buildConfigurationList = C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */; 202 | compatibilityVersion = "Xcode 13.0"; 203 | developmentRegion = en; 204 | hasScannedForEncodings = 0; 205 | knownRegions = ( 206 | en, 207 | Base, 208 | ); 209 | mainGroup = C6F53D6C28C6295600E0B11F; 210 | productRefGroup = C6F53D7628C6295600E0B11F /* Products */; 211 | projectDirPath = ""; 212 | projectRoot = ""; 213 | targets = ( 214 | C6F53D7428C6295600E0B11F /* jamf_list */, 215 | C6A33466294A227500FA22CD /* JamfList */, 216 | ); 217 | }; 218 | /* End PBXProject section */ 219 | 220 | /* Begin PBXResourcesBuildPhase section */ 221 | C6A33465294A227500FA22CD /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | C6A33472294A227600FA22CD /* Preview Assets.xcassets in Resources */, 226 | C6A3346E294A227600FA22CD /* Assets.xcassets in Resources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXResourcesBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | C6A33463294A227500FA22CD /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | C6A3347D294B4D2700FA22CD /* JamfObject.swift in Sources */, 238 | C6A3346C294A227500FA22CD /* ContentView.swift in Sources */, 239 | C6A3347B294B4D2700FA22CD /* JamfAuthToken.swift in Sources */, 240 | C6A33477294B46BE00FA22CD /* DetailView.swift in Sources */, 241 | C6A3347A294B4D1000FA22CD /* Computer.swift in Sources */, 242 | C6A33479294B4CB300FA22CD /* Computer-Sample.swift in Sources */, 243 | C6A3346A294A227500FA22CD /* JamfListApp.swift in Sources */, 244 | C6A3347C294B4D2700FA22CD /* JamfAPIError.swift in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | C6F53D7128C6295600E0B11F /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | C6873FB728D8A4D100FA893B /* Computer.swift in Sources */, 253 | C62CFA8A290AC9E900B2E481 /* Script.swift in Sources */, 254 | C6F53D8028C629D300E0B11F /* JamfList.swift in Sources */, 255 | C6873FB928D8B13700FA893B /* JamfAPIError.swift in Sources */, 256 | C6F53D8228C62CFF00E0B11F /* JamfAuthToken.swift in Sources */, 257 | C6C845312913CC1A00AF407A /* Keychain.swift in Sources */, 258 | C62CFA88290961D600B2E481 /* JamfObject.swift in Sources */, 259 | C6873FB528D8A37000FA893B /* Category.swift in Sources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXSourcesBuildPhase section */ 264 | 265 | /* Begin XCBuildConfiguration section */ 266 | C6A33473294A227600FA22CD /* Debug */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 270 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 272 | CODE_SIGN_ENTITLEMENTS = JamfList/JamfList.entitlements; 273 | CODE_SIGN_STYLE = Automatic; 274 | CURRENT_PROJECT_VERSION = 1; 275 | DEVELOPMENT_ASSET_PATHS = "\"JamfList/Preview Content\""; 276 | DEVELOPMENT_TEAM = JME5BW3F3R; 277 | ENABLE_HARDENED_RUNTIME = YES; 278 | ENABLE_PREVIEWS = YES; 279 | GENERATE_INFOPLIST_FILE = YES; 280 | INFOPLIST_KEY_CFBundleDisplayName = "Jamf List"; 281 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 282 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 283 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 284 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 285 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 286 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 287 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 288 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 289 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 290 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 291 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 292 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 293 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 294 | MACOSX_DEPLOYMENT_TARGET = 12.0; 295 | MARKETING_VERSION = 1.0; 296 | PRODUCT_BUNDLE_IDENTIFIER = com.scriptingosx.JamfList; 297 | PRODUCT_NAME = "$(TARGET_NAME)"; 298 | SDKROOT = auto; 299 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 300 | SWIFT_EMIT_LOC_STRINGS = YES; 301 | SWIFT_VERSION = 5.0; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | }; 304 | name = Debug; 305 | }; 306 | C6A33474294A227600FA22CD /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 312 | CODE_SIGN_ENTITLEMENTS = JamfList/JamfList.entitlements; 313 | CODE_SIGN_STYLE = Automatic; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEVELOPMENT_ASSET_PATHS = "\"JamfList/Preview Content\""; 316 | DEVELOPMENT_TEAM = JME5BW3F3R; 317 | ENABLE_HARDENED_RUNTIME = YES; 318 | ENABLE_PREVIEWS = YES; 319 | GENERATE_INFOPLIST_FILE = YES; 320 | INFOPLIST_KEY_CFBundleDisplayName = "Jamf List"; 321 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 322 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 323 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 324 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 325 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 326 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 327 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 328 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 329 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 330 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 331 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 332 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 333 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 334 | MACOSX_DEPLOYMENT_TARGET = 12.0; 335 | MARKETING_VERSION = 1.0; 336 | PRODUCT_BUNDLE_IDENTIFIER = com.scriptingosx.JamfList; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SDKROOT = auto; 339 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 340 | SWIFT_EMIT_LOC_STRINGS = YES; 341 | SWIFT_VERSION = 5.0; 342 | TARGETED_DEVICE_FAMILY = "1,2"; 343 | }; 344 | name = Release; 345 | }; 346 | C6F53D7A28C6295600E0B11F /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 353 | CLANG_ENABLE_MODULES = YES; 354 | CLANG_ENABLE_OBJC_ARC = YES; 355 | CLANG_ENABLE_OBJC_WEAK = YES; 356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_COMMA = YES; 359 | CLANG_WARN_CONSTANT_CONVERSION = YES; 360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 363 | CLANG_WARN_EMPTY_BODY = YES; 364 | CLANG_WARN_ENUM_CONVERSION = YES; 365 | CLANG_WARN_INFINITE_RECURSION = YES; 366 | CLANG_WARN_INT_CONVERSION = YES; 367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 373 | CLANG_WARN_STRICT_PROTOTYPES = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | COPY_PHASE_STRIP = NO; 379 | DEAD_CODE_STRIPPING = YES; 380 | DEBUG_INFORMATION_FORMAT = dwarf; 381 | ENABLE_STRICT_OBJC_MSGSEND = YES; 382 | ENABLE_TESTABILITY = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu11; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_OPTIMIZATION_LEVEL = 0; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | MACOSX_DEPLOYMENT_TARGET = 12.0; 398 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 399 | MTL_FAST_MATH = YES; 400 | ONLY_ACTIVE_ARCH = YES; 401 | SDKROOT = macosx; 402 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 403 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 404 | }; 405 | name = Debug; 406 | }; 407 | C6F53D7B28C6295600E0B11F /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_ANALYZER_NONNULL = YES; 412 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 413 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 414 | CLANG_ENABLE_MODULES = YES; 415 | CLANG_ENABLE_OBJC_ARC = YES; 416 | CLANG_ENABLE_OBJC_WEAK = YES; 417 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 418 | CLANG_WARN_BOOL_CONVERSION = YES; 419 | CLANG_WARN_COMMA = YES; 420 | CLANG_WARN_CONSTANT_CONVERSION = YES; 421 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 423 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 424 | CLANG_WARN_EMPTY_BODY = YES; 425 | CLANG_WARN_ENUM_CONVERSION = YES; 426 | CLANG_WARN_INFINITE_RECURSION = YES; 427 | CLANG_WARN_INT_CONVERSION = YES; 428 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 429 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 430 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 432 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 433 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 434 | CLANG_WARN_STRICT_PROTOTYPES = YES; 435 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 436 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 437 | CLANG_WARN_UNREACHABLE_CODE = YES; 438 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 439 | COPY_PHASE_STRIP = NO; 440 | DEAD_CODE_STRIPPING = YES; 441 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 442 | ENABLE_NS_ASSERTIONS = NO; 443 | ENABLE_STRICT_OBJC_MSGSEND = YES; 444 | GCC_C_LANGUAGE_STANDARD = gnu11; 445 | GCC_NO_COMMON_BLOCKS = YES; 446 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 447 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 448 | GCC_WARN_UNDECLARED_SELECTOR = YES; 449 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 450 | GCC_WARN_UNUSED_FUNCTION = YES; 451 | GCC_WARN_UNUSED_VARIABLE = YES; 452 | MACOSX_DEPLOYMENT_TARGET = 12.0; 453 | MTL_ENABLE_DEBUG_INFO = NO; 454 | MTL_FAST_MATH = YES; 455 | SDKROOT = macosx; 456 | SWIFT_COMPILATION_MODE = wholemodule; 457 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 458 | }; 459 | name = Release; 460 | }; 461 | C6F53D7D28C6295600E0B11F /* Debug */ = { 462 | isa = XCBuildConfiguration; 463 | buildSettings = { 464 | CLANG_ENABLE_MODULES = YES; 465 | CODE_SIGN_IDENTITY = "-"; 466 | CODE_SIGN_STYLE = Automatic; 467 | DEAD_CODE_STRIPPING = YES; 468 | DEVELOPMENT_TEAM = JME5BW3F3R; 469 | ENABLE_HARDENED_RUNTIME = YES; 470 | LD_RUNPATH_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "@executable_path/../Frameworks", 473 | "@loader_path/../Frameworks", 474 | ); 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 477 | SWIFT_VERSION = 5.0; 478 | }; 479 | name = Debug; 480 | }; 481 | C6F53D7E28C6295600E0B11F /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | CLANG_ENABLE_MODULES = YES; 485 | CODE_SIGN_IDENTITY = "-"; 486 | CODE_SIGN_STYLE = Automatic; 487 | DEAD_CODE_STRIPPING = YES; 488 | DEVELOPMENT_TEAM = JME5BW3F3R; 489 | ENABLE_HARDENED_RUNTIME = YES; 490 | LD_RUNPATH_SEARCH_PATHS = ( 491 | "$(inherited)", 492 | "@executable_path/../Frameworks", 493 | "@loader_path/../Frameworks", 494 | ); 495 | PRODUCT_NAME = "$(TARGET_NAME)"; 496 | SWIFT_VERSION = 5.0; 497 | }; 498 | name = Release; 499 | }; 500 | /* End XCBuildConfiguration section */ 501 | 502 | /* Begin XCConfigurationList section */ 503 | C6A33475294A227600FA22CD /* Build configuration list for PBXNativeTarget "JamfList" */ = { 504 | isa = XCConfigurationList; 505 | buildConfigurations = ( 506 | C6A33473294A227600FA22CD /* Debug */, 507 | C6A33474294A227600FA22CD /* Release */, 508 | ); 509 | defaultConfigurationIsVisible = 0; 510 | defaultConfigurationName = Release; 511 | }; 512 | C6F53D7028C6295600E0B11F /* Build configuration list for PBXProject "SwiftJamfAPI" */ = { 513 | isa = XCConfigurationList; 514 | buildConfigurations = ( 515 | C6F53D7A28C6295600E0B11F /* Debug */, 516 | C6F53D7B28C6295600E0B11F /* Release */, 517 | ); 518 | defaultConfigurationIsVisible = 0; 519 | defaultConfigurationName = Release; 520 | }; 521 | C6F53D7C28C6295600E0B11F /* Build configuration list for PBXNativeTarget "jamf_list" */ = { 522 | isa = XCConfigurationList; 523 | buildConfigurations = ( 524 | C6F53D7D28C6295600E0B11F /* Debug */, 525 | C6F53D7E28C6295600E0B11F /* Release */, 526 | ); 527 | defaultConfigurationIsVisible = 0; 528 | defaultConfigurationName = Release; 529 | }; 530 | /* End XCConfigurationList section */ 531 | }; 532 | rootObject = C6F53D6D28C6295600E0B11F /* Project object */; 533 | } 534 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/xcshareddata/xcschemes/jamf_list.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-5/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JamfList.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftJamfAPI.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | jamf_list.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | C6F53D7428C6295600E0B11F 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/CLI Tool/JamfList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfList.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | @main 11 | struct JamfList { 12 | enum JamfType: String { 13 | case computer 14 | case category 15 | case script 16 | } 17 | 18 | static func main() async { 19 | let args = CommandLine.arguments 20 | 21 | guard args.count > 3 22 | else { 23 | print("Not enough arguments") 24 | print("usage: jamf_list type server username [password]") 25 | exit(1) 26 | } 27 | 28 | // it is now safe to access `args[1...3]` 29 | guard let type = JamfType(rawValue: args[1]) 30 | else { 31 | print("\(args[1]) is not a known type") 32 | exit(2) 33 | } 34 | 35 | // connection info 36 | let server = args[2] 37 | let username = args[3] 38 | var password = "" 39 | 40 | // if there is a fourth argument, assign it to password 41 | if args.count > 4 { 42 | password = args[4] 43 | } else { 44 | // no password argument, get from keychain 45 | guard let passwordFromKeychain = try? Keychain.getPassword(service: server, account: username) 46 | else { 47 | print("Could not get password from keychain") 48 | exit(2) 49 | } 50 | password = passwordFromKeychain 51 | } 52 | 53 | // errors thrown in the do block will be processed in the catch block 54 | do { 55 | // get authentication token 56 | let auth = try await JamfAuthToken.get(server: server, username: username, password: password) 57 | 58 | switch type { 59 | case .category: 60 | let categories = try await Category.getAll(server: server, auth: auth) 61 | 62 | for category in categories { 63 | print(category.id, 64 | category.name, 65 | category.priority) 66 | } 67 | 68 | case .computer: 69 | // get computers 70 | let computers = try await Computer.getAll(server: server, auth: auth) 71 | 72 | for computer in computers { 73 | print(computer.id, 74 | computer.general.name) 75 | } 76 | 77 | case .script: 78 | //get scripts 79 | let scripts = try await Script.getAll(server:server, auth: auth) 80 | 81 | for script in scripts { 82 | print(script.id, 83 | script.name, 84 | "(\(script.scriptContents.split(separator: "\n").count) lines of code)") 85 | } 86 | } 87 | // catch the errors 88 | } catch JamfAPIError.badURL { 89 | print("could not create API URL") 90 | exit(11) 91 | } catch JamfAPIError.requestFailed { 92 | print("API request failed") 93 | exit(12) 94 | } catch JamfAPIError.http(let statusCode) { 95 | print("HTTP error: \(statusCode)") 96 | exit(13) 97 | } catch JamfAPIError.decode { 98 | print("could not decode JSON") 99 | exit(14) 100 | } catch { 101 | print("other error: \(error)") 102 | exit(99) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/CLI Tool/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // 4 | // Created by Armin Briegel on 2022-11-01. 5 | // 6 | 7 | import Foundation 8 | 9 | // built from the sample code here 10 | // https://developer.apple.com/documentation/security/keychain_services/keychain_items 11 | 12 | // I changed the class of the item to "internet password" 13 | // which makes it easier to manually create the item in keychain 14 | // and re-use items created when accessing the site in Safari 15 | 16 | // this reply here https://developer.apple.com/forums/thread/688612 17 | // recommended to use NSDictionary for the query dict to 18 | // avoid a lot of boiler plate type casting 19 | 20 | struct Keychain { 21 | enum KeychainError: Error { 22 | case noPassword // no matching item found 23 | case duplicateItem // found more than on matching item 24 | case unexpectedPasswordData // could not read item data 25 | case unhandledError(OSStatus) // other errors 26 | } 27 | 28 | static func save(password: String, service: String, account: String, label: String? = nil) throws { 29 | // if label is unset, use service as the label 30 | // this matches what keychain app does when creating a new item 31 | let safeLabel = label ?? service 32 | 33 | // when item already exists, use update instead 34 | if let _ = try? getPassword(service: service, account: account) { 35 | try update(password: password, service: service, account: account, label: label) 36 | return 37 | } 38 | 39 | let passwordData = Data(password.utf8) 40 | 41 | // create the Add Query Dict 42 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 43 | kSecAttrService: service, 44 | kSecAttrLabel: safeLabel, 45 | kSecAttrAccount: account, 46 | kSecValueData: passwordData] 47 | // add the item 48 | let status = SecItemAdd(query, nil) 49 | 50 | if status == errSecDuplicateItem { 51 | throw KeychainError.duplicateItem 52 | } 53 | 54 | guard status == errSecSuccess else { 55 | throw KeychainError.unhandledError(status) 56 | } 57 | } 58 | 59 | static func update(password: String, service: String, account: String, label: String? = nil) throws { 60 | let passwordData = Data(password.utf8) 61 | 62 | // if label is unset, use service as the label 63 | let safeLabel = label ?? service 64 | 65 | // prepare a search query 66 | let query: NSDictionary = [kSecAttrService: service, 67 | kSecAttrAccount: account, 68 | kSecClass: kSecClassInternetPassword] 69 | 70 | // prepare new attributes 71 | let attributes: NSDictionary = [kSecValueData: passwordData, 72 | kSecAttrLabel: safeLabel] 73 | // execute the update 74 | let status = SecItemUpdate(query, attributes) 75 | 76 | guard status != errSecItemNotFound else { 77 | throw KeychainError.noPassword 78 | } 79 | 80 | guard status == errSecSuccess else { 81 | throw KeychainError.unhandledError(status) 82 | } 83 | } 84 | 85 | static func getPassword(service: String, account: String) throws -> String { 86 | // create the search query 87 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 88 | kSecAttrService: service, 89 | kSecAttrAccount: account, 90 | kSecMatchLimit: kSecMatchLimitOne, 91 | kSecReturnData: true] 92 | 93 | var item: CFTypeRef? 94 | let status = SecItemCopyMatching(query, &item) 95 | 96 | guard status != errSecItemNotFound 97 | else { 98 | throw KeychainError.noPassword 99 | } 100 | 101 | guard status == errSecSuccess 102 | else { 103 | throw KeychainError.unhandledError(status) 104 | } 105 | 106 | // extract the result 107 | guard let passwordData = item as? Data, 108 | let password = String(data: passwordData, encoding: String.Encoding.utf8) 109 | else { 110 | throw KeychainError.unexpectedPasswordData 111 | } 112 | 113 | return password 114 | } 115 | 116 | static func delete(service: String, account: String) throws { 117 | // prepare the query dict 118 | let query: NSDictionary = [kSecClass: kSecClassInternetPassword, 119 | kSecAttrService: service, 120 | kSecAttrAccount: account] 121 | 122 | // delete the item 123 | let status = SecItemDelete(query) 124 | 125 | guard status != errSecItemNotFound 126 | else { 127 | throw KeychainError.noPassword 128 | } 129 | 130 | guard status == errSecSuccess else { 131 | throw KeychainError.unhandledError(status) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Category: JamfObject { 11 | var id: String 12 | var name: String 13 | var priority: Int 14 | 15 | static let getAllEndpoint = "/api/v1/categories" 16 | } 17 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/Computer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Computer.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Computer: JamfObject { 11 | 12 | struct General: Codable { 13 | var name: String 14 | var assetTag: String? 15 | var lastEnrolledDate: Date 16 | var userApprovedMdm: Bool 17 | } 18 | 19 | struct Hardware: Codable { 20 | var model: String 21 | var modelIdentifier: String 22 | var serialNumber: String 23 | var appleSilicon: Bool 24 | } 25 | 26 | struct OperatingSystem: Codable { 27 | var name: String 28 | var version: String 29 | var build: String 30 | } 31 | 32 | var id: String 33 | var general: General 34 | var hardware: Hardware 35 | var operatingSystem: OperatingSystem 36 | 37 | // MARK: JamfObject implementation 38 | static let getAllEndpoint = "/api/v1/computers-inventory" 39 | 40 | // override getAllURLComponents to add query items 41 | static func getAllURLComponents(server: String) throws -> URLComponents { 42 | guard var components = URLComponents(string: server) 43 | else { 44 | throw JamfAPIError.badURL 45 | } 46 | components.path = self.getAllEndpoint 47 | 48 | components.queryItems = [ URLQueryItem(name: "section", value: "GENERAL"), 49 | URLQueryItem(name: "section", value: "HARDWARE"), 50 | URLQueryItem(name: "section", value: "OPERATING_SYSTEM"), 51 | URLQueryItem(name: "sort", value: "id:asc") ] 52 | return components 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAPIError.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum JamfAPIError: Error { 11 | case requestFailed 12 | case http(Int) 13 | case authentication 14 | case decode 15 | case encode 16 | case badURL 17 | case noCredentials 18 | } 19 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfAuthToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfAuthToken.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-09-05. 6 | // 7 | 8 | import Foundation 9 | 10 | struct JamfAuthToken: Codable { 11 | var token: String 12 | var expires: String 13 | 14 | 15 | /** Get an authentication Token from the server */ 16 | static func get(server: String, username: String, password: String) async throws -> JamfAuthToken { 17 | 18 | // MARK: Prepare Request 19 | // encode user name and password 20 | let base64 = "\(username):\(password)" 21 | .data(using: String.Encoding.utf8)! 22 | .base64EncodedString() 23 | 24 | // assemble the URL for the Jamf API 25 | guard var components = URLComponents(string: server) else { 26 | throw JamfAPIError.badURL 27 | } 28 | components.path="/api/v1/auth/token" 29 | guard let url = components.url else { 30 | throw JamfAPIError.badURL 31 | } 32 | 33 | // MARK: Send Request and get Data 34 | 35 | // create the request 36 | var authRequest = URLRequest(url: url) 37 | authRequest.httpMethod = "POST" 38 | authRequest.addValue("Basic " + base64, forHTTPHeaderField: "Authorization") 39 | 40 | // send request and get data 41 | guard let (data, response) = try? await URLSession.shared.data(for: authRequest) 42 | else { 43 | throw JamfAPIError.requestFailed 44 | } 45 | 46 | // MARK: Handle Errors 47 | 48 | // check the response code 49 | let authStatusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 50 | if authStatusCode != 200 { 51 | throw JamfAPIError.http(authStatusCode) 52 | } 53 | 54 | // print(String(data: data, encoding: .utf8) ?? "no data") 55 | 56 | // MARK: Parse JSON returned 57 | let decoder = JSONDecoder() 58 | 59 | guard let auth = try? decoder.decode(JamfAuthToken.self, from: data) 60 | else { 61 | throw JamfAPIError.decode 62 | } 63 | 64 | return auth 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/ConnectSheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectSheet.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ConnectSheet: View { 11 | @Binding var show: Bool 12 | @ObservedObject var controller: JamfController 13 | 14 | @State var saveInKeychain: Bool = false 15 | 16 | @AppStorage("server") var server = "" 17 | @AppStorage("username") var username = "" 18 | 19 | var body: some View { 20 | VStack { 21 | VStack { 22 | Form { 23 | TextField("Jamf Server URL", text: $server) 24 | .frame(width: 400) 25 | TextField("Username", text: $username) 26 | SecureField("Password", text: $controller.password) 27 | Toggle("Save in Keychain", isOn: $saveInKeychain) 28 | } 29 | }.padding() 30 | HStack { 31 | Spacer() 32 | Button("Cancel") { 33 | show = false 34 | } 35 | .keyboardShortcut(.escape) 36 | Button("Connect") { 37 | Task { await connect() } 38 | } 39 | .keyboardShortcut(.defaultAction) 40 | }.padding() 41 | } 42 | } 43 | 44 | func connect() async { 45 | // hide this sheet 46 | show = false 47 | 48 | if saveInKeychain { 49 | try? Keychain.save(password: controller.password, service: server, account: username) 50 | } 51 | 52 | await controller.load() 53 | } 54 | } 55 | 56 | struct ConnectSheet_Previews: PreviewProvider { 57 | static var previews: some View { 58 | ConnectSheet(show: .constant(true), controller: JamfController()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @StateObject var controller = JamfController() 12 | 13 | var body: some View { 14 | NavigationView { 15 | if controller.isLoading { 16 | VStack { 17 | ProgressView().progressViewStyle(.circular) 18 | Text("Loading…").foregroundColor(Color.gray) 19 | } 20 | } else { 21 | List(controller.computers) { item in 22 | NavigationLink(item.general.name, destination: DetailView(computer: item)) 23 | } 24 | } 25 | // placeholder view for detail 26 | Text("Select an Item") 27 | .foregroundColor(Color.gray) 28 | } 29 | .sheet(isPresented: $controller.needsCredentials) { 30 | ConnectSheet( 31 | show: $controller.needsCredentials, 32 | controller: controller 33 | ) 34 | } 35 | .onAppear { 36 | Task { 37 | await controller.load() 38 | } 39 | } 40 | .toolbar(id: "Main") { 41 | ToolbarItem(id: "Error") { 42 | if controller.hasError { 43 | Image(systemName: "exclamationmark.triangle.fill") 44 | .foregroundStyle(.secondary, .yellow) 45 | .imageScale(.large) 46 | } 47 | } 48 | ToolbarItem(id: "Connect") { 49 | Button(action: { 50 | controller.needsCredentials = true 51 | }) { 52 | Label("Connect", systemImage: controller.connected ? "bolt.horizontal.fill" : "bolt.horizontal") 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | struct ContentView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ContentView() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/DetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailView.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-15. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DetailView: View { 11 | var computer: Computer 12 | 13 | var body: some View { 14 | VStack(alignment: .leading) { 15 | Text(computer.general.name) 16 | .font(.title) 17 | Divider() 18 | Text(computer.general.lastEnrolledDate.description) 19 | Text(computer.hardware.serialNumber) 20 | Text(computer.hardware.appleSilicon 21 | ? "Apple silicon" : "Intel") 22 | Text("macOS \(computer.operatingSystem.version)") 23 | Spacer() 24 | } 25 | .padding() 26 | } 27 | } 28 | 29 | struct DetailView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | DetailView(computer: Computer.sampleMacBookAir) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/JamfController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfController.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-20. 6 | // 7 | 8 | import Foundation 9 | 10 | class JamfController: ObservableObject { 11 | @Published var computers: [Computer] = [] 12 | 13 | @Published var isLoading = false 14 | @Published var needsCredentials = false 15 | @Published var connected = false 16 | @Published var hasError = false 17 | 18 | var server: String { UserDefaults.standard.string(forKey: "server") ?? "" } 19 | var username: String { UserDefaults.standard.string(forKey: "username") ?? "" } 20 | var password = "" 21 | 22 | var auth: JamfAuthToken? 23 | 24 | @MainActor 25 | func load() async { 26 | isLoading = true 27 | defer { 28 | isLoading = false 29 | } 30 | 31 | if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { 32 | // preview mode return sample data 33 | computers = Computer.samples 34 | return 35 | } 36 | 37 | // not in preview mode 38 | 39 | // attempt to get an auth token 40 | await connect() 41 | 42 | // only continue if connected 43 | guard connected, let auth = auth else { return } 44 | 45 | if let fetchedComputers = try? await Computer.getAll(server: server, auth: auth) { 46 | computers = fetchedComputers 47 | } else { 48 | hasError = true 49 | } 50 | } 51 | 52 | @MainActor 53 | func connect() async { 54 | // do we have all credentials? 55 | if server.isEmpty || username.isEmpty { 56 | needsCredentials = true 57 | connected = false 58 | return 59 | } 60 | 61 | if password.isEmpty { 62 | // try to get password from keychain 63 | guard let pwFromKeychain = try? Keychain.getPassword(service: server, account: username) 64 | else { 65 | needsCredentials = true 66 | connected = false 67 | return 68 | } 69 | password = pwFromKeychain 70 | } 71 | 72 | if auth == nil { 73 | // no token yet, get one 74 | auth = try? await JamfAuthToken.get(server: server, username: username, password: password) 75 | if auth == nil { 76 | // couldn't get a token, most likely the credentials are wrong 77 | hasError = true 78 | needsCredentials = true 79 | connected = false 80 | return 81 | } 82 | } 83 | 84 | // we have a token, all is good 85 | needsCredentials = false 86 | hasError = false 87 | connected = true 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/JamfList.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/JamfListApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfListApp.swift 3 | // JamfList 4 | // 5 | // Created by Armin Briegel on 2022-12-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct JamfListApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfList/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/JamfObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JamfObject.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-26. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JamfObject: Codable, Identifiable { 11 | var id: String { get set } 12 | 13 | static var getAllEndpoint: String { get } 14 | 15 | /** Build the URL for the request to fetch all objects */ 16 | static func getAllURLComponents(server: String) throws -> URLComponents 17 | 18 | /** Gets a list of all objects from the Jamf Pro Server */ 19 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] 20 | } 21 | 22 | extension JamfObject { 23 | 24 | /** build the URL for the request to fetch all categories */ 25 | static func getAllURLComponents(server: String) throws -> URLComponents { 26 | // assemble the URL for the Jamf API 27 | guard var components = URLComponents(string: server) 28 | else { 29 | throw JamfAPIError.badURL 30 | } 31 | components.path = self.getAllEndpoint 32 | 33 | return components 34 | } 35 | 36 | /** Gets a list of all categories from the Jamf Pro Server */ 37 | static func getAll(server: String, auth: JamfAuthToken) async throws -> [Self] { 38 | // MARK: Prepare Request 39 | let components = try getAllURLComponents(server: server) 40 | 41 | guard let url = components.url 42 | else { 43 | throw JamfAPIError.badURL 44 | } 45 | 46 | // print("Request URL: \(url.absoluteString)") 47 | 48 | // MARK: Send Request and get Data 49 | // create the request 50 | var request = URLRequest(url: url) 51 | request.httpMethod = "GET" 52 | request.addValue("Bearer " + auth.token, forHTTPHeaderField: "Authorization") 53 | 54 | // send request and get data 55 | guard let (data, response) = try? await URLSession.shared.data(for: request) 56 | else { 57 | throw JamfAPIError.requestFailed 58 | } 59 | 60 | // MARK: Handle Error 61 | // check the response code 62 | let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 63 | if statusCode != 200 { 64 | // error getting token 65 | throw JamfAPIError.http(statusCode) 66 | } 67 | 68 | // print(String(data: data, encoding: .utf8) ?? "no data") 69 | 70 | // MARK: Parse JSON Data 71 | let decoder = JSONDecoder() 72 | 73 | // set date decoding to match Jamf's date format 74 | let dateFormatter = DateFormatter() 75 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 76 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 77 | 78 | do { 79 | let result = try decoder.decode(JamfResults.self, from: data) 80 | 81 | return result.results 82 | 83 | // handle decoding errors 84 | // see DecodingError documentation for details 85 | } catch let DecodingError.dataCorrupted(context) { 86 | print("\(context.codingPath): data corrupted: \(context.debugDescription)") 87 | } catch let DecodingError.keyNotFound(key, context) { 88 | print("\(context.codingPath): key \(key) not found: \(context.debugDescription)") 89 | } catch let DecodingError.valueNotFound(value, context) { 90 | print("\(context.codingPath): value \(value) not found: \(context.debugDescription)") 91 | } catch let DecodingError.typeMismatch(type, context) { 92 | print("\(context.codingPath): type \(type) mismatch: \(context.debugDescription)") 93 | } catch { 94 | print("error: ", error) 95 | } 96 | 97 | throw JamfAPIError.decode 98 | } 99 | 100 | } 101 | 102 | struct JamfResults: Codable { 103 | var totalCount: Int 104 | var results: [T] 105 | } 106 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script.swift 3 | // jamf_list 4 | // 5 | // Created by Armin Briegel on 2022-10-27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Script: JamfObject { 11 | enum Priority: String, Codable { 12 | case before = "BEFORE" 13 | case after = "AFTER" 14 | } 15 | 16 | var id: String 17 | var name: String = "" 18 | var info: String = "" 19 | var notes: String = "" 20 | var priority: Priority = .after 21 | var parameter4: String = "" 22 | var parameter5: String = "" 23 | var parameter6: String = "" 24 | var parameter7: String = "" 25 | var parameter8: String = "" 26 | var parameter9: String = "" 27 | var parameter10: String = "" 28 | var parameter11: String = "" 29 | var osRequirements: String = "" 30 | var scriptContents: String = "" 31 | var categoryId: String = "-1" 32 | var categoryName: String = "" 33 | 34 | static var getAllEndpoint = "/api/v1/scripts" 35 | } 36 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scriptingosx/SwiftAPITutorial/fd0ebc3936831344de4c348fb22751167bf0d85a/SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/project.xcworkspace/xcuserdata/armin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/xcshareddata/xcschemes/jamf_list.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftJamfAPI-6/SwiftJamfAPI.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JamfList.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | SwiftJamfAPI.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | jamf_list.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | C6F53D7428C6295600E0B11F 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------