ruby-jss 0.6.5 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

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
+ }