1import Cocoa
2
3//login class
4class jamfInfo {
5 var server: String?
6 var id: String?
7 var name: String?
8 var username: String = ""
9 var password: String = ""
10}
11
12struct userSearch: Decodable {
13 let user: listOfUsers
14
15 struct listOfUsers: Decodable {
16 let name: String
17 let full_name: String
18 let email: String
19 let phone_number: String
20 let links: listOfmobiledevices
21
22 struct listOfmobiledevices: Decodable {
23 let mobiledevices: [mobiledevices]
24
25 struct mobiledevices: Decodable {
26 let name: String
27 let id: Int
28
29 }
30 }
31 }
32}
33
34struct extensionAttribute: Decodable {
35 let mobiledevices: mobiledevicesEA
36
37 struct mobiledevicesEA: Decodable {
38 let extension_attributes: [EA]
39
40 struct EA: Decodable {
41 let id: Int
42 let name: String
43 let value: String
44 }
45
46 }
47
48}
49
50class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
51
52 @IBOutlet var userInfo: NSTextField!
53 @IBOutlet var tableView: NSTableView!
54 @IBOutlet var JamfPassword: NSSecureTextField!
55 @IBOutlet var jamfUser: NSTextField!
56 @IBOutlet var userNameField: NSTextFieldCell!
57 @IBOutlet var toggleButton: NSButtonCell!
58
59 var listOfmobiledevices = [String]()
60 var listOfmobiledevicesIDs = [Int]()
61 var listOfEAs = [String]()
62
63 var prefs = jamfInfo()
64
65 override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
66 let second = segue.destinationController as! PrefController
67 readPlist()
68 prefs.username = jamfUser.stringValue
69 prefs.password = JamfPassword.stringValue
70 second.representedObject = prefs
71 listOfmobiledevices.removeAll()
72 tableView.reloadData()
73
74
75 }
76
77 override func viewDidLoad() {
78 super.viewDidLoad()
79
80 readPlist()
81 if prefs.server != nil {
82 let keychainVar = try? keychainlogin(server: prefs.server!)
83 if keychainVar != nil {
84 prefs.username = keychainVar!.KCUsername
85 prefs.password = keychainVar!.KCPassword
86 jamfUser.stringValue = prefs.username
87 JamfPassword.stringValue = prefs.password
88 }
89 }
90
91 toggleButton.isEnabled = false
92 // Do any additional setup after loading the view.
93 }
94
95 @IBAction func preferencesMenuItemSelected(_ sender: Any) {
96 self.performSegue(withIdentifier: "prefSegue", sender: self)
97 }
98
99 @IBAction func doSomething(_ sender: Any) {
100 readPlist()
101 getTableView()
102 }
103
104 func numberOfRows(in tableView: NSTableView) -> Int {
105 return listOfmobiledevices.count
106 }
107
108 func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
109
110 guard let vw = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView else { return nil }
111
112 if tableColumn?.title == "Computer List" {
113 vw.textField?.stringValue = listOfmobiledevices[row]
114
115 } else {
116 var eaOutput: Data?
117 readPlist()
118 eaOutput = getJamfData(url: "(prefs.server!)JSSResource/mobiledevices/id/(listOfmobiledevicesIDs[row])/subset/ExtensionAttributes").jamfDataOutput
119
120 let EAdecoder = JSONDecoder()
121 let EADetails = try! EAdecoder.decode(extensionAttribute.self, from: eaOutput!)
122
123 for EAs in EADetails.mobiledevices.extension_attributes {
124 if EAs.id == Int(prefs.id!) {
125 vw.textField?.stringValue = EAs.value
126 listOfEAs.insert(EAs.value, at: 0)
127 break;
128 } else {
129 vw.textField?.stringValue = ""
130 listOfEAs.insert("", at: 0)
131 }
132 }
133
134 }
135 return vw
136 }
137
138
139 func tableViewSelectionDidChange(_ notification: Notification) {
140
141 if tableView.selectedRow != -1 {
142 toggleButton.isEnabled = true
143 } else {
144 toggleButton.isEnabled = false
145 }
146
147 }
148
149 func getJamfData(url: String) -> (jamfDataOutput: Data, jamfResponse: Int) {
150 let loginData = "(jamfUser.stringValue):(JamfPassword.stringValue)".data(using: String.Encoding.utf8)
151 let base64LoginString = loginData!.base64EncodedString()
152 let headers = ["Accept": "application/json",
153 "Authorization": "Basic (String(describing: base64LoginString))"]
154
155 let request = NSMutableURLRequest(url: NSURL(string: url)! as URL,
156 cachePolicy: .useProtocolCachePolicy,
157 timeoutInterval: 10.0)
158 request.httpMethod = "GET"
159 request.allHTTPHeaderFields = headers
160
161
162 var dataReturn: Data?
163 var ResponseCode: HTTPURLResponse?
164
165
166 let dispatchGroup = DispatchGroup()
167 dispatchGroup.enter()
168
169 let session = URLSession.shared
170
171 let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
172 if let APIdata = data {
173 dataReturn = APIdata
174 ResponseCode = response as? HTTPURLResponse
175
176 }
177 dispatchGroup.leave()
178 })
179 dataTask.resume()
180 dispatchGroup.wait()
181 if dataReturn == nil {
182 dataReturn = "".data(using: String.Encoding.utf8)
183
184 }
185 var ResponseStatusCode: Int
186 if ResponseCode == nil {
187 ResponseStatusCode = 1
188 } else {
189 ResponseStatusCode = (ResponseCode?.statusCode)!
190 }
191 return(dataReturn!, ResponseStatusCode)
192
193 }
194
195 @IBAction func toggle(_ sender: Any) {
196 readPlist()
197 guard tableView.selectedRow != -1 else { return }
198 var action: String = ""
199
200 if listOfEAs[tableView.selectedRow] != "Enabled" {
201 action = "Enabled"
202 } else {
203 action = "Disabled"
204 }
205 var xmldata: String
206
207 let requestURL = "(prefs.server!)JSSResource/mobiledevices/id/(listOfmobiledevicesIDs[tableView.selectedRow])"
208
209 xmldata = "<?xml version="1.0" encoding="UTF-8" standalone="no"?><mobiledevices><extension_attributes><extension_attribute><id>" + prefs.id! + "</id><name>" + prefs.name! + "</name><type>String</type><value>" + action + "</value></extension_attribute></extension_attributes></mobiledevice>"
210
211 let loginData = "(jamfUser.stringValue):(JamfPassword.stringValue)".data(using: String.Encoding.utf8)
212 let base64LoginString = loginData!.base64EncodedString()
213 let postData = NSData(data: xmldata.data(using: String.Encoding.utf8)!)
214 let headers = ["Content-Type": "text/xml", "Authorization": "Basic (String(describing: base64LoginString))"]
215 let request = NSMutableURLRequest(url: NSURL(string: requestURL)! as URL,cachePolicy: .useProtocolCachePolicy,timeoutInterval: 10.0)
216 request.httpMethod = "PUT"
217 request.allHTTPHeaderFields = headers
218 request.httpBody = postData as Data
219
220 let dispatchGroup = DispatchGroup()
221 dispatchGroup.enter()
222
223 let session = URLSession.shared
224 let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
225
226 dispatchGroup.leave()
227 }
228 )
229
230 dataTask.resume()
231
232 dispatchGroup.wait()
233
234
235 getTableView()
236 toggleButton.isEnabled = true
237
238 }
239
240 func errorOccured(typeOfError: String){
241 let alert = NSAlert()
242 alert.messageText = "Error"
243 alert.informativeText = typeOfError
244 alert.runModal()
245 }
246
247 func getTableView(){
248 listOfmobiledevices.removeAll()
249 tableView.reloadData()
250
251 let myData = getJamfData(url: "(prefs.server!)JSSResource/users/name/(userNameField.stringValue)")
252
253
254 let decoder = JSONDecoder()
255 do {
256 switch myData.jamfResponse {
257 case 401:
258 errorOccured(typeOfError: "Login Incorrect.")
259 case 400:
260 errorOccured(typeOfError: "Bad Request: You sent a request that this server could not understand.")
261 case 404:
262 errorOccured(typeOfError: "User not found.")
263 case 1:
264 errorOccured(typeOfError: "Could not connect to Server")
265 case 200:
266 let userdetails = try decoder.decode(userSearch.self, from: myData.jamfDataOutput)
267
268 let userInfoString = """
269 (userdetails.user.full_name)
270 (userdetails.user.email)
271 (userdetails.user.phone_number)
272 """
273 let emailStringLength = userdetails.user.email.count + 1
274 let attributedString = NSMutableAttributedString(string: userInfoString)
275 attributedString.addAttribute(.link, value: "mailto: (userdetails.user.email)", range: NSRange(location: userdetails.user.full_name.count, length: emailStringLength ))
276
277 userInfo.attributedStringValue = attributedString
278
279 for entries in userdetails.user.links.mobiledevices {
280 listOfmobiledevicesIDs.insert(entries.id as Int, at: 0)
281 listOfmobiledevices.insert(entries.name as String, at: 0)
282 tableView.insertRows(at: IndexSet(integer: 0))
283 }
284 default:
285 errorOccured(typeOfError: "A connection error occured. Sorry")
286 //NSApplication.shared.terminate(self)
287 }
288
289 } catch {
290 errorOccured(typeOfError: "Unknown error occured. Sorry")
291 //NSApplication.shared.terminate(self)
292 }
293 }
294
295 func keychainlogin(server: String) throws -> (KCUsername: String, KCPassword: String) {
296 let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
297 kSecAttrServer as String: server,
298 kSecMatchLimit as String: kSecMatchLimitOne,
299 kSecReturnAttributes as String: true,
300 kSecReturnData as String: true]
301
302 var item: CFTypeRef?
303 let status = SecItemCopyMatching(query as CFDictionary, &item)
304 guard status != errSecItemNotFound else { throw KeychainError.noPassword }
305 guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
306
307 guard let existingItem = item as? [String : Any],
308 let passwordData = existingItem[kSecValueData as String] as? Data,
309 let keychainpassword = String(data: passwordData, encoding: String.Encoding.utf8),
310 let keychainaccount = existingItem[kSecAttrAccount as String] as? String
311 else {
312 throw KeychainError.unexpectedPasswordData
313 }
314
315 return(keychainaccount, keychainpassword)
316 }
317
318 func readPlist(){
319
320 prefs.server = UserDefaults.standard.string(forKey: "jss_URL") ?? ""
321 prefs.name = UserDefaults.standard.string(forKey: "EA_NAME") ?? ""
322 prefs.id = UserDefaults.standard.string(forKey: "EA_ID") ?? ""
323
324 }
325}