ruby-jss 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +57 -5
  3. data/lib/jss.rb +78 -94
  4. data/lib/jss/api_connection.rb +8 -0
  5. data/lib/jss/api_object.rb +126 -102
  6. data/lib/jss/api_object/creatable.rb +33 -15
  7. data/lib/jss/api_object/distribution_point.rb +4 -1
  8. data/lib/jss/api_object/extension_attribute.rb +1 -1
  9. data/lib/jss/api_object/package.rb +121 -187
  10. data/lib/jss/api_object/policy.rb +590 -251
  11. data/lib/jss/api_object/script.rb +92 -128
  12. data/lib/jss/api_object/self_servable.rb +93 -117
  13. data/lib/jss/api_object/updatable.rb +12 -27
  14. data/lib/jss/api_object/uploadable.rb +12 -15
  15. data/lib/jss/client.rb +178 -156
  16. data/lib/jss/db_connection.rb +34 -49
  17. data/lib/jss/ruby_extensions/string.rb +25 -21
  18. data/lib/jss/version.rb +1 -1
  19. data/lib/jss/webhooks.rb +52 -0
  20. data/lib/jss/webhooks/README.md +269 -0
  21. data/lib/jss/webhooks/configuration.rb +212 -0
  22. data/lib/jss/webhooks/data/sample_handlers/RestAPIOperation-executable +90 -0
  23. data/lib/jss/webhooks/data/sample_handlers/RestAPIOperation.rb +44 -0
  24. data/lib/jss/webhooks/data/sample_jsons/ComputerAdded.json +27 -0
  25. data/lib/jss/webhooks/data/sample_jsons/ComputerCheckIn.json +27 -0
  26. data/lib/jss/webhooks/data/sample_jsons/ComputerInventoryCompleted.json +27 -0
  27. data/lib/jss/webhooks/data/sample_jsons/ComputerPolicyFinished.json +27 -0
  28. data/lib/jss/webhooks/data/sample_jsons/ComputerPushCapabilityChanged.json +27 -0
  29. data/lib/jss/webhooks/data/sample_jsons/JSSShutdown.json +14 -0
  30. data/lib/jss/webhooks/data/sample_jsons/JSSStartup.json +14 -0
  31. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceCheckIn.json +26 -0
  32. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceCommandCompleted.json +26 -0
  33. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceEnrolled.json +26 -0
  34. data/lib/jss/webhooks/data/sample_jsons/MobileDevicePushSent.json +26 -0
  35. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceUnEnrolled.json +26 -0
  36. data/lib/jss/webhooks/data/sample_jsons/PatchSoftwareTitleUpdated.json +14 -0
  37. data/lib/jss/webhooks/data/sample_jsons/PushSent.json +11 -0
  38. data/lib/jss/webhooks/data/sample_jsons/RestAPIOperation.json +15 -0
  39. data/lib/jss/webhooks/data/sample_jsons/SCEPChallenge.json +10 -0
  40. data/lib/jss/webhooks/data/sample_jsons/SmartGroupComputerMembershipChange.json +13 -0
  41. data/lib/jss/webhooks/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +13 -0
  42. data/lib/jss/webhooks/event.rb +138 -0
  43. data/lib/jss/webhooks/event/computer_added.rb +37 -0
  44. data/lib/jss/webhooks/event/computer_check_in.rb +37 -0
  45. data/lib/jss/webhooks/event/computer_inventory_completed.rb +37 -0
  46. data/lib/jss/webhooks/event/computer_policy_finished.rb +37 -0
  47. data/lib/jss/webhooks/event/computer_push_capability_changed.rb +37 -0
  48. data/lib/jss/webhooks/event/handlers.rb +191 -0
  49. data/lib/jss/webhooks/event/jss_shutdown.rb +37 -0
  50. data/lib/jss/webhooks/event/jss_startup.rb +37 -0
  51. data/lib/jss/webhooks/event/mobile_device_check_in.rb +37 -0
  52. data/lib/jss/webhooks/event/mobile_device_command_completed.rb +37 -0
  53. data/lib/jss/webhooks/event/mobile_device_enrolled.rb +37 -0
  54. data/lib/jss/webhooks/event/mobile_device_push_sent.rb +37 -0
  55. data/lib/jss/webhooks/event/mobile_device_unenrolled.rb +37 -0
  56. data/lib/jss/webhooks/event/patch_software_title_updated.rb +37 -0
  57. data/lib/jss/webhooks/event/push_sent.rb +37 -0
  58. data/lib/jss/webhooks/event/rest_api_operation.rb +37 -0
  59. data/lib/jss/webhooks/event/scep_challenge.rb +37 -0
  60. data/lib/jss/webhooks/event/smart_group_computer_membership_change.rb +37 -0
  61. data/lib/jss/webhooks/event/smart_group_mobile_device_membership_change.rb +37 -0
  62. data/lib/jss/webhooks/event/webhook.rb +39 -0
  63. data/lib/jss/webhooks/event_objects.rb +111 -0
  64. data/lib/jss/webhooks/event_objects/computer.rb +48 -0
  65. data/lib/jss/webhooks/event_objects/jss.rb +35 -0
  66. data/lib/jss/webhooks/event_objects/mobile_device.rb +47 -0
  67. data/lib/jss/webhooks/event_objects/patch_software_title_update.rb +37 -0
  68. data/lib/jss/webhooks/event_objects/push.rb +32 -0
  69. data/lib/jss/webhooks/event_objects/rest_api_operation.rb +36 -0
  70. data/lib/jss/webhooks/event_objects/scep_challenge.rb +31 -0
  71. data/lib/jss/webhooks/event_objects/smart_group.rb +34 -0
  72. data/lib/jss/webhooks/server_app.rb +36 -0
  73. data/lib/jss/webhooks/server_app/routes.rb +26 -0
  74. data/lib/jss/webhooks/server_app/routes/handle_webhook_event.rb +38 -0
  75. data/lib/jss/webhooks/server_app/routes/home.rb +36 -0
  76. data/lib/jss/webhooks/server_app/self_signed_cert.rb +64 -0
  77. data/lib/jss/webhooks/server_app/server.rb +59 -0
  78. data/lib/jss/webhooks/version.rb +31 -0
  79. metadata +63 -4
@@ -0,0 +1,212 @@
1
+ ### Copyright 2016 Pixar
2
+ ###
3
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
4
+ ### with the following modification; you may not use this file except in
5
+ ### compliance with the Apache License and the following modification to it:
6
+ ### Section 6. Trademarks. is deleted and replaced with:
7
+ ###
8
+ ### 6. Trademarks. This License does not grant permission to use the trade
9
+ ### names, trademarks, service marks, or product names of the Licensor
10
+ ### and its affiliates, except as required to comply with Section 4(c) of
11
+ ### the License and to reproduce the content of the NOTICE file.
12
+ ###
13
+ ### You may obtain a copy of the Apache License at
14
+ ###
15
+ ### http://www.apache.org/licenses/LICENSE-2.0
16
+ ###
17
+ ### Unless required by applicable law or agreed to in writing, software
18
+ ### distributed under the Apache License with the above modification is
19
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ ### KIND, either express or implied. See the Apache License for the specific
21
+ ### language governing permissions and limitations under the Apache License.
22
+ ###
23
+ ###
24
+
25
+ module JSSWebHooks
26
+
27
+ # The configuration object
28
+ class Configuration
29
+
30
+ include Singleton
31
+
32
+ # The location of the default config file
33
+ DEFAULT_CONF_FILE = Pathname.new '/etc/jsswebhooks.conf'
34
+
35
+ ### The attribute keys we maintain, and the type they should be stored as
36
+ CONF_KEYS = {
37
+ server_port: :to_i,
38
+ server_engine: :to_sym,
39
+ handler_dir: :to_s,
40
+ handler_computer_added: :to_s,
41
+ handler_computer_check_in: :to_s,
42
+ handler_computer_inventory_completed: :to_s,
43
+ handler_computer_policy_finished: :to_s,
44
+ handler_computer_push_capability_changed: :to_s,
45
+ handler_jss_shutdown: :to_s,
46
+ handler_jss_startup: :to_s,
47
+ handler_mobile_device_check_in: :to_s,
48
+ handler_mobile_device_command_complete: :to_s,
49
+ handler_mobile_device_enrolled: :to_s,
50
+ handler_mobile_device_push_sent: :to_s,
51
+ handler_mobile_device_unenrolled: :to_s,
52
+ handler_patch_software_title_updated: :to_s,
53
+ handler_push_sent: :to_s,
54
+ handler_rest_api_operation: :to_s,
55
+ handler_scep_challenge: :to_s,
56
+ handler_smart_group_computer_membership_change: :to_s,
57
+ handler_smart_group_mobile_device_membership_change: :to_s
58
+ }.freeze
59
+
60
+ #####################################
61
+ ### Class Variables
62
+ #####################################
63
+
64
+ #####################################
65
+ ### Class Methods
66
+ #####################################
67
+
68
+ #####################################
69
+ ### Attributes
70
+ #####################################
71
+
72
+ # automatically create accessors for all the CONF_KEYS
73
+ CONF_KEYS.keys.each { |k| attr_accessor k }
74
+
75
+ #####################################
76
+ ### Constructor
77
+ #####################################
78
+
79
+ ###
80
+ ### Initialize!
81
+ ###
82
+ def initialize
83
+ read_global
84
+ end
85
+
86
+ #####################################
87
+ ### Public Instance Methods
88
+ #####################################
89
+
90
+ ###
91
+ ### Clear all values
92
+ ###
93
+ ### @return [void]
94
+ ###
95
+ def clear_all
96
+ CONF_KEYS.keys.each { |k| send "#{k}=".to_sym, nil }
97
+ end
98
+
99
+ ###
100
+ ### (Re)read the global prefs, if it exists.
101
+ ###
102
+ ### @return [Boolean] was the file loaded?
103
+ ###
104
+ def read_global
105
+ return false unless DEFAULT_CONF_FILE.file? && DEFAULT_CONF_FILE.readable?
106
+ read DEFAULT_CONF_FILE
107
+ end
108
+
109
+ ###
110
+ ### Clear the settings and reload the prefs file, or another file if provided
111
+ ###
112
+ ### @param file[String,Pathname] a non-standard prefs file to load
113
+ ###
114
+ ### @return [Boolean] was the file reloaded?
115
+ ###
116
+ def reload(file = DEFAULT_CONF_FILE)
117
+ file = Pathname.new file
118
+ return false unless file.file? and file.readable?
119
+ clear_all
120
+ read file
121
+ end
122
+
123
+ ###
124
+ ### Save the prefs into a file
125
+ ###
126
+ ### @param file[Symbol,String,Pathname] either :user, :global, or an arbitrary file to save.
127
+ ###
128
+ ### @return [void]
129
+ ###
130
+ def save(file)
131
+ path = Pathname.new(file)
132
+
133
+ # file already exists? read it in and update the values.
134
+ # Don't overwrite it, since the user might have comments
135
+ # in there.
136
+ if path.readable?
137
+ data = path.read
138
+
139
+ # go thru the known attributes/keys
140
+ CONF_KEYS.keys.sort.each do |k|
141
+ # if the key exists, update it.
142
+ if data =~ /^#{k}:/
143
+ data.sub!(/^#{k}:.*$/, "#{k}: #{send k}")
144
+
145
+ # if not, add it to the end unless it's nil
146
+ else
147
+ data += "\n#{k}: #{send k}" unless send(k).nil?
148
+ end # if data =~ /^#{k}:/
149
+ end # each do |k|
150
+
151
+ else # not readable, make a new file
152
+ data = ''
153
+ CONF_KEYS.keys.sort.each do |k|
154
+ data << "#{k}: #{send k}\n" unless send(k).nil?
155
+ end
156
+ end # if path readable
157
+
158
+ # make sure we end with a newline, the save it.
159
+ data << "\n" unless data.end_with?("\n")
160
+ path.jss_save data
161
+ end # save file
162
+
163
+ ###
164
+ ### Print out the current settings to stdout
165
+ ###
166
+ ### @return [void]
167
+ ###
168
+ def print
169
+ CONF_KEYS.keys.sort.each { |k| puts "#{k}: #{send k}" }
170
+ end
171
+
172
+ #####################################
173
+ ### Private Instance Methods
174
+ #####################################
175
+ private
176
+
177
+ ###
178
+ ### Read in any prefs file
179
+ ###
180
+ ### @param file[String,Pathname] the file to read
181
+ ###
182
+ ### @return [Boolean] was the file read?
183
+ ###
184
+ def read(file)
185
+ available_conf_keys = CONF_KEYS.keys
186
+ Pathname.new(file).read.each_line do |line|
187
+ # skip blank lines and those starting with #
188
+ next if line =~ /^\s*(#|$)/
189
+
190
+ line.strip =~ /^(\w+?):\s*(\S.*)$/
191
+ key = Regexp.last_match(1)
192
+ next unless key
193
+ attr = key.to_sym
194
+ setter = "#{key}=".to_sym
195
+ value = Regexp.last_match(2).strip
196
+
197
+ next unless available_conf_keys.include? attr
198
+
199
+ # convert the value to the correct class
200
+ value = value.send(CONF_KEYS[attr]) if value
201
+
202
+ send(setter, value)
203
+ end # do line
204
+ true
205
+ end # read file
206
+
207
+ end # class Configuration
208
+
209
+ # The single instance of Configuration
210
+ CONFIG = JSSWebHooks::Configuration.instance
211
+
212
+ end # module
@@ -0,0 +1,90 @@
1
+ #!/bin/bash
2
+
3
+ ### Copyright 2016 Pixar
4
+ ###
5
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
6
+ ### with the following modification; you may not use this file except in
7
+ ### compliance with the Apache License and the following modification to it:
8
+ ### Section 6. Trademarks. is deleted and replaced with:
9
+ ###
10
+ ### 6. Trademarks. This License does not grant permission to use the trade
11
+ ### names, trademarks, service marks, or product names of the Licensor
12
+ ### and its affiliates, except as required to comply with Section 4(c) of
13
+ ### the License and to reproduce the content of the NOTICE file.
14
+ ###
15
+ ### You may obtain a copy of the Apache License at
16
+ ###
17
+ ### http://www.apache.org/licenses/LICENSE-2.0
18
+ ###
19
+ ### Unless required by applicable law or agreed to in writing, software
20
+ ### distributed under the Apache License with the above modification is
21
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22
+ ### KIND, either express or implied. See the Apache License for the specific
23
+ ### language governing permissions and limitations under the Apache License.
24
+ ###
25
+ ###
26
+
27
+ # NOTE: Native bash doesn't have any easy way to parse JSON.
28
+ # Instead, you should really use a tool like 'jq' https://stedolan.github.io/jq/.
29
+ #
30
+ # If you have jq, set its correct path in the JQ variable below.
31
+ #
32
+ # If not, this script will use a hacky, VERY inappropriate and breakable
33
+ # way to parse JSON!
34
+ # But, it works enough to demonstrate the idea of using bash to write a handler.
35
+
36
+ # Enter your path to jq
37
+ JQ=/usr/local/bin/jq
38
+
39
+ # read all of stdin into a variable
40
+ while read line ; do JSON="$JSON $line" ; done
41
+
42
+ # use jq if we can
43
+ if [ -x "$JQ" ] ; then
44
+
45
+ hookname=`echo $JSON | "$JQ" -r '.webhook.name'`
46
+ authname=`echo $JSON | "$JQ" -r '.event.authorizedUsername'`
47
+ otype=`echo $JSON | "$JQ" -r '.event.objectTypeName'`
48
+ oname=`echo $JSON | "$JQ" -r '.event.objectName'`
49
+ oid=`echo $JSON | "$JQ" -r '.event.objectID'`
50
+ optype=`echo $JSON | "$JQ" -r '.event.restAPIOperationType'`
51
+
52
+ # otherwise, hacky bash parsing.
53
+ else
54
+
55
+ # use a comma as the field separator for the for-loop
56
+ IFS=','
57
+
58
+ # loop thru comma-separated chunks
59
+ for chunk in $JSON ; do
60
+ hookre='"name": "(.+)"'
61
+ [[ $chunk =~ $hookre ]] && hookname="${BASH_REMATCH[1]}"
62
+ authunamere='"authorizedUsername": "(.+)"'
63
+ [[ $chunk =~ $authunamere ]] && authname="${BASH_REMATCH[1]}"
64
+ otypere='"objectTypeName": "(.+)"'
65
+ [[ $chunk =~ $otypere ]] && otype="${BASH_REMATCH[1]}"
66
+ onamere='"objectName": "(.+)"'
67
+ [[ $chunk =~ $onamere ]] && oname="${BASH_REMATCH[1]}"
68
+ oidre='"objectID": (.+)'
69
+ [[ $chunk =~ $oidre ]] && oid="${BASH_REMATCH[1]}"
70
+ optypere='"restAPIOperationType": "(.+)"'
71
+ [[ $chunk =~ $optypere ]] && optype="${BASH_REMATCH[1]}"
72
+ done
73
+
74
+ fi # if [ -x "$JQ" ] ; then
75
+
76
+ # make an 'action' out of the operation type
77
+ # or exit if we don't know or don't care.
78
+ case $optype in
79
+ GET) exit 0 ;;
80
+ PUT) action=create ;;
81
+ POST) action=update ;;
82
+ DELETE) action=delete ;;
83
+ *) exit 15 ;;
84
+ esac
85
+
86
+ # output
87
+ echo "The JSS WebHook named '${hookname}' was just triggered.
88
+ It indicates that Casper user '${authname}' just used the JSS API to ${action}
89
+ the JSS ${otype}named '${oname}' (id ${oid})
90
+ "
@@ -0,0 +1,44 @@
1
+ ### Copyright 2016 Pixar
2
+ ###
3
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
4
+ ### with the following modification; you may not use this file except in
5
+ ### compliance with the Apache License and the following modification to it:
6
+ ### Section 6. Trademarks. is deleted and replaced with:
7
+ ###
8
+ ### 6. Trademarks. This License does not grant permission to use the trade
9
+ ### names, trademarks, service marks, or product names of the Licensor
10
+ ### and its affiliates, except as required to comply with Section 4(c) of
11
+ ### the License and to reproduce the content of the NOTICE file.
12
+ ###
13
+ ### You may obtain a copy of the Apache License at
14
+ ###
15
+ ### http://www.apache.org/licenses/LICENSE-2.0
16
+ ###
17
+ ### Unless required by applicable law or agreed to in writing, software
18
+ ### distributed under the Apache License with the above modification is
19
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ ### KIND, either express or implied. See the Apache License for the specific
21
+ ### language governing permissions and limitations under the Apache License.
22
+ ###
23
+ ###
24
+
25
+ JSSWebHooks.event_handler do |event|
26
+ eobject = event.event_object
27
+ whook = event.webhook
28
+
29
+ REPORT_ACTIONS = {
30
+ 'PUT' => 'update',
31
+ 'POST' => 'create',
32
+ 'DELETE' => 'delete'
33
+ }.freeze unless defined? REPORT_ACTIONS
34
+
35
+ action = REPORT_ACTIONS[eobject.restAPIOperationType]
36
+
37
+ return nil unless action
38
+
39
+ puts <<-ENDMSG
40
+ The JSS WebHook named '#{whook.name}' was just triggered.
41
+ It indicates that Casper user '#{eobject.authorizedUsername}' just used the JSS API to #{action}
42
+ the JSS #{eobject.objectTypeName} named '#{eobject.objectName}' (id #{eobject.objectID})
43
+ ENDMSG
44
+ end
@@ -0,0 +1,27 @@
1
+ {
2
+ "webhook": {
3
+ "id": 1,
4
+ "name": "ComputerAdded Webhook",
5
+ "webhookEvent": "ComputerAdded"
6
+ },
7
+ "event": {
8
+ "udid": "99848AF0-xxxx-XXXX-xxxx-B79B9F65D7F8",
9
+ "deviceName": "wanderer",
10
+ "model": "MacBookPro10,1",
11
+ "macAddress": "yy:E0:yy:16:yy:E9",
12
+ "alternateMacAddress": "32:xx:1A:xx:xx:60",
13
+ "serialNumber": "VM87S764H831",
14
+ "osVersion": "10.15.2",
15
+ "osBuild": "48673",
16
+ "userDirectoryID": "-1",
17
+ "username": "username",
18
+ "realName": "User Name",
19
+ "emailAddress": "user@school.edu",
20
+ "phone": "x3369",
21
+ "position": "Apple Peeler",
22
+ "department": "MacBack",
23
+ "building": "Soho",
24
+ "room": "s167",
25
+ "jssID": 1322
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "webhook": {
3
+ "id": 2,
4
+ "name": "ComputerCheckin Webhook",
5
+ "webhookEvent": "ComputerCheckin"
6
+ },
7
+ "event": {
8
+ "udid": "99848AF0-xxxx-XXXX-xxxx-B79B9F65D7F8",
9
+ "deviceName": "wanderer",
10
+ "model": "MacBookPro10,1",
11
+ "macAddress": "yy:E0:yy:16:yy:E9",
12
+ "alternateMacAddress": "32:xx:1A:xx:xx:60",
13
+ "serialNumber": "VM87S764H831",
14
+ "osVersion": "10.15.2",
15
+ "osBuild": "48673",
16
+ "userDirectoryID": "-1",
17
+ "username": "username",
18
+ "realName": "User Name",
19
+ "emailAddress": "user@school.edu",
20
+ "phone": "x3369",
21
+ "position": "Apple Peeler",
22
+ "department": "MacBack",
23
+ "building": "Soho",
24
+ "room": "s167",
25
+ "jssID": 1322
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "webhook": {
3
+ "id": 3,
4
+ "name": "ComputerInventoryCompleted Webhook",
5
+ "webhookEvent": "ComputerInventoryCompleted"
6
+ },
7
+ "event": {
8
+ "udid": "99848AF0-xxxx-XXXX-xxxx-B79B9F65D7F8",
9
+ "deviceName": "wanderer",
10
+ "model": "MacBookPro10,1",
11
+ "macAddress": "yy:E0:yy:16:yy:E9",
12
+ "alternateMacAddress": "32:xx:1A:xx:xx:60",
13
+ "serialNumber": "VM87S764H831",
14
+ "osVersion": "10.15.2",
15
+ "osBuild": "48673",
16
+ "userDirectoryID": "-1",
17
+ "username": "username",
18
+ "realName": "User Name",
19
+ "emailAddress": "user@school.edu",
20
+ "phone": "x3369",
21
+ "position": "Apple Peeler",
22
+ "department": "MacBack",
23
+ "building": "Soho",
24
+ "room": "s167",
25
+ "jssID": 1322
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "webhook": {
3
+ "id": 4,
4
+ "name": "ComputerPolicyFinished Webhook",
5
+ "webhookEvent": "ComputerPolicyFinished"
6
+ },
7
+ "event": {
8
+ "udid": "99848AF0-xxxx-XXXX-xxxx-B79B9F65D7F8",
9
+ "deviceName": "wanderer",
10
+ "model": "MacBookPro10,1",
11
+ "macAddress": "yy:E0:yy:16:yy:E9",
12
+ "alternateMacAddress": "32:xx:1A:xx:xx:60",
13
+ "serialNumber": "VM87S764H831",
14
+ "osVersion": "10.15.2",
15
+ "osBuild": "48673",
16
+ "userDirectoryID": "-1",
17
+ "username": "username",
18
+ "realName": "User Name",
19
+ "emailAddress": "user@school.edu",
20
+ "phone": "x3369",
21
+ "position": "Apple Peeler",
22
+ "department": "MacBack",
23
+ "building": "Soho",
24
+ "room": "s167",
25
+ "jssID": 1322
26
+ }
27
+ }