forked from jamf/API_Scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mdGroupEnroll.py
449 lines (201 loc) · 8.5 KB
/
mdGroupEnroll.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env python
# Limitations:
# - Only one device per student is supported
# - Classes may not contain a + or / character as the API cannot parse these at this time
# Settings
jssAddr = "https://localhost:8443" # JSS server address with or without https:// and port
jssUser = "api" # JSS login username (API privileges must be set)
jssPass = "api" # JSS login password
csvPath = "/tmp/studentCourse.csv" # Path and file name for the input CSV file
# Imports
from urllib2 import urlopen, URLError, HTTPError, Request
from xml.dom import minidom
from xml.sax import make_parser, ContentHandler
from sys import exc_info, argv, stdout, stdin
from getpass import getpass
import base64
import csv
import re
def main():
global jssAddr
global jssUser
global jssPass
global csvPath
updateMDGroups = JSSUpdateMobileDeviceGroups()
studentList = updateMDGroups.getCSVData(csvPath)
updateMDGroups.updateGroups(jssAddr, jssUser, jssPass, studentList)
class JSSUpdateMobileDeviceGroups:
def __init__(self):
self.numMobileDevicesUpdated = 0
self.deviceMap = dict()
self.unmanagedDeviceMap = dict()
def getCSVData(self, csvPath):
# Read in CSV
csvinput = open(csvPath, 'rU')
reader = csv.reader(csvinput)
return reader
def updateGroups(self, jssAddr, jssUser, jssPass, studentList):
# Cache mobile device ID to student username mapping
self.grabMobileDeviceData(jssAddr, jssUser, jssPass)
# Assign student device to the classes (groups)
url = jssAddr + "/JSSResource/mobiledevicegroups"
grabber = CasperGroupPUTHandler(jssUser, jssPass)
for studentLine in studentList:
if studentLine[0] != "Student ID":
self.handleStudentDeviceAssignment(grabber, url, studentLine)
print "Successfully updated %d devices in the JSS." % self.numMobileDevicesUpdated
def grabMobileDeviceData(self, jssAddr, jssUser, jssPass):
url = jssAddr + "/JSSResource/mobiledevices"
grabber = CasperDeviceXMLGrabber(jssUser, jssPass)
grabber.parseXML(url, CasperMobileDeviceListHandler(grabber, self.deviceMap, self.unmanagedDeviceMap))
def handleStudentDeviceAssignment(self, grabber, url, studentLine):
# Create mobile device studentLine XML...
if studentLine[0] in self.deviceMap:
if "/" in studentLine[1] or "+" in studentLine[1]:
print "Error: User: %s, Class: %s, Error: Class contains forward slash or ends in plus character" % (studentLine[0], studentLine[1])
else:
studentDeviceID = self.deviceMap[studentLine[0]]
newGroupAssignment = self.createNewGroupElement(studentDeviceID, studentLine[1])
self.handleGroupPUT(grabber, url, studentLine[1], newGroupAssignment)
else:
print "Error: User: %s, Class: %s, Error: Could not find a mobile device match for student username or device is unmanaged" % (studentLine[0], studentLine[1])
def handleGroupPUT(self, grabber, url, className, newGroupAssignment):
# PUT new XML
apiClassURLRAW = url + "/name/" + className
apiClassURL = apiClassURLRAW.replace (' ', '+')
apiClassName = className.replace ('+', '')
apiClassName = apiClassName.replace (' ', '+')
###########UNCOMMENT NEXT TWO LINES FOR DEBUG MODE#############
#print "PUT-ing URL %s: " % (apiClassURL)
#print newGroupAssignment.toprettyxml()
putStatus = grabber.openXMLStream("%s/name/%s" % (url, apiClassName), newGroupAssignment)
if putStatus is None:
self.numMobileDevicesUpdated += 1
def createNewGroupElement(self, studentDeviceID, groupName):
global eventValues
newGroupAssignment = minidom.Document()
group = self.appendEmptyElement(newGroupAssignment, newGroupAssignment, "mobile_device_group")
self.appendNewTextElement(newGroupAssignment, group, "is_smart", "false")
self.appendNewTextElement(newGroupAssignment, group, "name", groupName)
groupAdditions = self.appendEmptyElement(newGroupAssignment, group, "mobile_device_additions")
deviceElement = self.appendEmptyElement(newGroupAssignment, groupAdditions, "mobile_device")
self.appendNewTextElement(newGroupAssignment, deviceElement, "id", studentDeviceID)
return newGroupAssignment
def appendEmptyElement(self, doc, section, newElementTag):
newElement = doc.createElement(newElementTag)
section.appendChild(newElement)
return newElement
def appendNewTextElement(self, doc, section, newElementTag, newElementValue):
newElement = self.appendEmptyElement(doc, section, newElementTag)
newValueElement = doc.createTextNode(newElementValue)
newElement.appendChild(newValueElement)
return newElement
class CasperDeviceXMLGrabber:
def __init__(self, jssUser, jssPass):
self.jssUser = jssUser
self.jssPass = jssPass
def parseXML(self, url, handler):
req = Request(url)
base64string = base64.encodestring('%s:%s' % (self.jssUser, self.jssPass))[:-1]
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)
try:
MobileDeviceList = urlopen(req)
except (URLError, HTTPError) as urlError: # Catch errors related to urlopen()
print "Error when opening URL: " + urlError.__str__()
exit(1)
except: # Catch any unexpected problems and bail out of the program
print "Unexpected error:", exc_info()[0]
exit(1)
parser = make_parser()
parser.setContentHandler(handler)
parser.parse(MobileDeviceList)
class CasperGroupPUTHandler:
def __init__(self, jssUser, jssPass):
self.jssUser = jssUser
self.jssPass = jssPass
def parseXML(self, url):
return self.openXMLStream(url, None)
def openXMLStream(self, url, xmlout):
try:
if xmlout is None:
req = Request(url)
else:
req = Request(url, data=xmlout.toxml())
req.add_header('Content-Type', 'text/xml')
req.get_method = lambda: 'PUT'
base64string = base64.encodestring('%s:%s' % (self.jssUser, self.jssPass))[:-1]
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)
xmldata = urlopen(req)
if xmlout is None:
xmldoc = minidom.parse(xmldata)
else:
xmldoc = None
return xmldoc
except (URLError, HTTPError) as urlError: # Catch errors related to urlopen()
if urlError.code == 404:
if xmlout is None:
req = Request(url)
else:
req = Request(url, data=xmlout.toxml())
req.add_header('Content-Type', 'text/xml')
req.get_method = lambda: 'POST'
base64string = base64.encodestring('%s:%s' % (self.jssUser, self.jssPass))[:-1]
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)# Debug log in line below# print "POSTING TO: %s XML: %s" % (url, xmlout.toxml()) xmldata = urlopen(req)
if xmlout is None:
xmldoc = minidom.parse(xmldata)
else:
xmldoc = None
return xmldoc
print "Error when opening URL %s - %s " % (url, urlError.__str__())
return "Error"
except: # Catch any unexpected problems and bail out of the program
print "Unexpected error:", exc_info()[0]
exit(1)
# This class is used to parse the /mobiledevices list to get all of the ids and usernames
class CasperMobileDeviceListHandler(ContentHandler):
def __init__(self, grabber, deviceMap, unmanagedDeviceMap):
ContentHandler.__init__(self)
self.grabber = grabber
self.deviceMap = deviceMap
self.unmanagedDeviceMap = unmanagedDeviceMap
self.currentID = ""
self.inID = False
self.inSize = False
self.inUsername = False
self.inManaged = False
def startElement(self, tag, attributes):
if tag == "id":
self.inID = True
elif tag == "username":
self.inUsername = True
elif tag == "managed":
self.inManaged = True
elif tag == "size":
self.inSize = True
def endElement(self, tag):
self.inID = False
self.inSize = False
self.inManaged = False
self.inUsername = False
if tag == "mobiledevices":
print "Finished collecting mobile devices for lookup"
def characters(self, data):
if self.inID:
self.currentID = data
elif self.inUsername:
if data != "" and self.currentID not in self.unmanagedDeviceMap.values():
self.deviceMap[data] = self.currentID
elif self.inManaged:
if data != "true":
self.unmanagedDeviceMap[data] = self.currentID
elif self.inSize:
self.numDevices = data
print "Collecting data for " + data + " Mobile Device(s)..."
if __name__ == "__main__":
main()