chook 1.1.2 → 1.1.5b1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c4ec862f7ad2fdbbc49daeb67862dd096a7524be67a5d5901e1d948a7857ae2
4
- data.tar.gz: 3da70bf1d76edbf10371abc819b645e3620bd259a7973d9bdc39e71be74f95b1
3
+ metadata.gz: a2a39df5350d3ae528addf0baaebe5a122b1701b08e2e503afd9b0500910120c
4
+ data.tar.gz: a8a277843eb2606251f8c6000cd25b2e11c60a94c69998046e59f5c0010c4489
5
5
  SHA512:
6
- metadata.gz: 1a81f5307c33b300e307a28fa59bfd008f1513d1b135c46855a4f0a41b8033269b346790d65f3c168423bb5c363cb17e04062b780ef73b3190b05e59b3e48339
7
- data.tar.gz: 81fb57becdf2dc470be52fc287b955ccf6cbbb72dd5cab955924b925ff2cfc66d1d2c2dad35cfd7bdbdbc9f4cf0c7eb410100f939b2ffdad78a47a82e8b1bad2
6
+ metadata.gz: 519c4bf9ab0662a599eeef50e6e6bb637203f22c24a4cdc121bc94f28f239f856d9ff1380422259b67b64cb19554d18e76abb39ceabeb87ea408a9fde6398876
7
+ data.tar.gz: ad11db916b235f4bc0aaca73aae01b36f38c222555a76004fa4ac7f7e34efa32bc4fb80768366536dfd29a6f56ac5ec0912c6ac7eb1be63e62c7e03cec44170b
data/CHANGES.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Chook Change Log
2
2
 
3
+ ## v 1.1.5 2020-12-11
4
+
5
+ - Add support for handling DeviceAddedToDEP webhook events
6
+ - Updated the handling of ComputerPolicyFinished events to reflect new JSON structure via a PolicyFinished subject class.
7
+ NOTE: The computer info in the PolicyFinished subject for ComputerPolicyFinished events is located in a hash in the 'computer' attribute of the subject. So to get the SN of the computer that finished a policy, you'd use `event.subject.computer[:serialNumber]`. See the file ..lib/chook/subject/policy_finished.rb.
8
+
9
+ ### IMPORTANT Note:
10
+ Version 1.1.5 is probably the last release of v.1x for chook. Version 2 will be a major reworking of the code. While the general principles will remain the same, a lot will be simplified, some will be jettisoned (e.g. the whole TestEvent aspect) and hopefully lots will be optimized to better handle more and faster incoming webhooks. We'll get some test code up to Github asap.
11
+
12
+ ## v 1.1.4, 2020-08-10
13
+
14
+ - Set the server process name to 'chook' - some OS utilities will see it
15
+ - remove event START messages from info logging, now only visible when log level is debug.
16
+ - Don't use ruby object IDs as event ids - ruby reuses them.
17
+ - Server uptime is displayed on the simple admin web UI.
18
+
19
+ ## v 1.1.3, 2019-10-28
20
+
21
+ - Named Handlers! You can create a handler with any file name, and put it in /Library/Application Support/Chook/NamedHandlers then call it specifically from a webhook in Jamf Pro using the url http[s]://your.chook.server.com/handler/handler-filename
22
+
3
23
  ## v 1.1.2, 2019-01-24
4
24
 
5
25
  - code cleanup & bugfixes
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Documentation is a work in progress. Please [get in touch](mailto:chook@pixar.com) for assistance. <3
4
4
 
5
+ ### IMPORTANT Note:
6
+ Version 1.1.5 is probably the last release of v.1x for chook. Version 2 will be a major reworking of the code. While the general principles will remain the same, a lot will be simplified, some will be jettisoned (e.g. the whole TestEvent aspect) and hopefully lots will be optimized to better handle more and faster incoming webhooks. We'll get some test code up to Github asap.
7
+
8
+
5
9
  - [Introduction](#introduction)
6
10
  - [Installing Chook](#installing-chook)
7
11
  - [The Server](#the-server)
@@ -444,8 +448,9 @@ the `Chook::Subjects` module.
444
448
  | Chook::HandledEvents::ComputerAddedEvent | Chook::HandledSubjects::Computer |
445
449
  | Chook::HandledEvents::ComputerCheckInEvent | Chook::HandledSubjects::Computer |
446
450
  | Chook::HandledEvents::ComputerInventoryCompletedEvent | Chook::HandledSubjects::Computer |
447
- | Chook::HandledEvents::ComputerPolicyFinishedEvent | Chook::HandledSubjects::Computer |
451
+ | Chook::HandledEvents::ComputerPolicyFinishedEvent | Chook::HandledSubjects::PolicyFinished |
448
452
  | Chook::HandledEvents::ComputerPushCapabilityChangedEvent | Chook::HandledSubjects::Computer |
453
+ | Chook::HandledEvents::DeviceAddedToDEP | Chook::HandledSubjects::DEPDevice |
449
454
  | Chook::HandledEvents::JSSShutdownEvent | Chook::HandledSubjects::JSS |
450
455
  | Chook::HandledEvents::JSSStartupEvent | Chook::HandledSubjects::JSS |
451
456
  | Chook::HandledEvents::MobileDeviceCheckinEvent | Chook::HandledSubjects::MobileDevice |
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ Process.setproctitle('chook')
4
+
3
5
  ### Copyright 2017 Pixar
4
6
 
5
7
  ###
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.pixar.chook-server</string>
7
+ <key>StandardOutPath</key>
8
+ <string>/var/log/chook-server.log</string>
9
+ <key>StandardErrorPath</key>
10
+ <string>/var/log/chook-server.log</string>
11
+ <key>ProgramArguments</key>
12
+ <array>
13
+ <string>/usr/local/pixar/bin/chook-server</string>
14
+ </array>
15
+ <key>RunAtLoad</key>
16
+ <true/>
17
+ <key>KeepAlive</key>
18
+ <true/>
19
+ </dict>
20
+ </plist>
@@ -39,11 +39,7 @@ Chook.event_handler do |event|
39
39
 
40
40
  action = APIOpHandler::REPORT_ACTIONS[event.subject.restAPIOperationType]
41
41
 
42
- return nil unless action
42
+ break unless action
43
43
 
44
- puts <<-ENDMSG
45
- The JSS WebHook named '#{event.webhook_name}' was just triggered.
46
- It indicates that Casper user '#{event.subject.authorizedUsername}' just used the JSS API to #{action}
47
- the JSS #{event.subject.objectTypeName} named '#{event.subject.objectName}' (id #{event.subject.objectID})
48
- ENDMSG
44
+ event.logger.info "API #{action}: #{event.subject.objectTypeName} '#{event.subject.objectName}' (id #{event.subject.objectID})"
49
45
  end
@@ -65,8 +65,9 @@ module Chook
65
65
  'ComputerAdded' => Chook::Subject::COMPUTER,
66
66
  'ComputerCheckIn' => Chook::Subject::COMPUTER,
67
67
  'ComputerInventoryCompleted' => Chook::Subject::COMPUTER,
68
- 'ComputerPolicyFinished' => Chook::Subject::COMPUTER,
68
+ 'ComputerPolicyFinished' => Chook::Subject::POLICY_FINISHED,
69
69
  'ComputerPushCapabilityChanged' => Chook::Subject::COMPUTER,
70
+ 'DeviceAddedToDEP' => Chook::Subject::DEP_DEVICE,
70
71
  'JSSShutdown' => Chook::Subject::JAMF_SOFTWARE_SERVER,
71
72
  'JSSStartup' => Chook::Subject::JAMF_SOFTWARE_SERVER,
72
73
  'MobileDeviceCheckIn' => Chook::Subject::MOBILE_DEVICE,
@@ -94,6 +95,9 @@ module Chook
94
95
 
95
96
  #### Attrbutes common to all events
96
97
 
98
+ # @return [String] A unique identifier for this chook event
99
+ attr_reader :id
100
+
97
101
  # @return [Integer] The webhook id in the JSS that caused this event
98
102
  attr_reader :webhook_id
99
103
 
@@ -128,6 +132,7 @@ module Chook
128
132
  # event. Any not provided will be nil.
129
133
  #
130
134
  def initialize(**args)
135
+ @id = "#{Time.now.to_i}-#{SecureRandom.urlsafe_base64 8}"
131
136
  if args[:raw_json]
132
137
  @raw_json = args[:raw_json]
133
138
  @parsed_json = JSON.parse @raw_json, symbolize_names: true
@@ -118,10 +118,15 @@ module Chook
118
118
  super raw_json: raw_event_json
119
119
  end # init
120
120
 
121
+ def event_class_name
122
+ self.class.const_get(Chook::Event::EVENT_NAME_CONST)
123
+ end
124
+
125
+ # Run all the general handlers for this event class
126
+ #
121
127
  def handle
122
- handler_key = self.class.const_get(Chook::Event::EVENT_NAME_CONST)
123
- handlers = Handlers.handlers[handler_key]
124
- return 'No handlers loaded' unless handlers.is_a? Array
128
+ handlers = Handlers.handlers[event_class_name]
129
+ return "No handlers loaded for #{event_class_name} events" unless handlers.is_a? Array
125
130
 
126
131
  handlers.each do |handler|
127
132
  case handler
@@ -135,9 +140,23 @@ module Chook
135
140
  # the handle method should return a string,
136
141
  # which is the body of the HTTP result for
137
142
  # POSTing the event
138
- "Processed by #{handlers.count} handlers"
143
+ "Processed by #{handlers.count} general handlers"
139
144
  end # def handle
140
145
 
146
+ # run a single handler specified by filename
147
+ #
148
+ def handle_by_name(handler_to_run)
149
+ handler = Handlers.named_handlers[handler_to_run]
150
+ return "No named handler '#{handler_to_run}'" unless handler
151
+
152
+ if handler.is_a? Pathname
153
+ pipe_to_executable handler
154
+ else
155
+ handle_with_proc handler
156
+ end # if
157
+ "Processed by named handler '#{handler_to_run}'"
158
+ end
159
+
141
160
  # TODO: these threads will die midstream when the server stops.
142
161
  # Find a way to .join them or otherwise clean them up.
143
162
 
@@ -70,6 +70,11 @@ module Chook
70
70
 
71
71
  DEFAULT_HANDLER_DIR = '/Library/Application Support/Chook'.freeze
72
72
 
73
+ # Handlers that are only called by name using the route:
74
+ # post '/handler/:handler_name'
75
+ # are located in this subdirection of the handler directory
76
+ NAMED_HANDLER_SUBDIR = 'NamedHandlers'.freeze
77
+
73
78
  # internal handler files must match this regex somewhere
74
79
  INTERNAL_HANDLER_BLOCK_START_RE = /Chook.event_handler( ?\{| do) *\|/
75
80
 
@@ -101,12 +106,63 @@ module Chook
101
106
  # (The objects also have a #handler_file attribute that is the Pathname)
102
107
  #
103
108
  def self.handlers
104
- @handlers
109
+ @handlers ||= {}
110
+ end
111
+
112
+ # Handlers can check Chook::HandledEvent::Handlers.reloading?
113
+ # and do stuff if desired.
114
+ def self.reloading?
115
+ @reloading
116
+ end
117
+
118
+ # getter for @named_handlers
119
+ # These handlers are called by name via the route
120
+ # " post '/handler/:handler_name'"
121
+ #
122
+ # They are not tied to any event type by their filenames
123
+ # its up to the writers of the handlers to make sure
124
+ # the webhook that calls them is sending the correct event
125
+ # type.
126
+ #
127
+ # The data structure of @named_handlers is a
128
+ # Hash of Strings to Pathnames or Anon Objects:
129
+ # {
130
+ # handler_filename => Pathname or Obj,
131
+ # handler_filename => Pathname or Obj,
132
+ # handler_filename => Pathname or Obj
133
+ # }
134
+ #
135
+ # @return [Hash {String => Pathname, Proc}]
136
+ def self.named_handlers
137
+ @named_handlers ||= {}
138
+ end
139
+
140
+ # the Pathname objects for all loaded handlers
141
+ #
142
+ # @return [Array<Pathname>]
143
+ #
144
+ def self.all_handler_paths
145
+ hndlrs = named_handlers.values
146
+ hndlrs += handlers.values.flatten
147
+ hndlrs.map do |hndlr|
148
+ hndlr.is_a?(Pathname) ? hndlr : hndlr.handler_file
149
+ end
105
150
  end
106
- @handlers ||= {}
107
151
 
108
152
  # Load all the event handlers from the handler_dir or an arbitrary dir.
109
153
  #
154
+ #
155
+ # Handler files must be either:
156
+ # - An executable file, which will have the raw JSON from the JSS piped
157
+ # to it's stdin when executed
158
+ # or
159
+ # - A non-executable file of ruby code like this:
160
+ # Chook.event_handler do |event|
161
+ # # your code goes here.
162
+ # end
163
+ #
164
+ # (see the Chook README for details about writing the ruby handlers)
165
+ #
110
166
  # @param from_dir [String, Pathname] directory from which to load the
111
167
  # handlers. Defaults to CONFIG.handler_dir or DEFAULT_HANDLER_DIR if
112
168
  # config is unset
@@ -120,31 +176,45 @@ module Chook
120
176
  # use default if needed
121
177
  from_dir ||= DEFAULT_HANDLER_DIR
122
178
  handler_dir = Pathname.new(from_dir)
179
+ named_handler_dir = handler_dir + NAMED_HANDLER_SUBDIR
123
180
  load_type = 'Loading'
124
181
 
125
182
  if reload
183
+ @reloading = true
126
184
  @handlers = {}
185
+ @named_handlers = {}
127
186
  @loaded_handler = nil
128
187
  load_type = 'Re-loading'
129
188
  end
130
189
 
131
- Chook.logger.info "#{load_type} handlers from directory: #{handler_dir}"
132
-
133
- unless handler_dir.directory? && handler_dir.readable?
134
- Chook.logger.error "Handler directory '#{from_dir}' not a readable directory. No handlers loaded. "
135
- return
190
+ # General Handlers
191
+ Chook.logger.info "#{load_type} general handlers from directory: #{handler_dir}"
192
+ if handler_dir.directory? && handler_dir.readable?
193
+ handler_dir.children.each do |handler_file|
194
+ load_general_handler(handler_file) if handler_file.file? && handler_file.readable?
195
+ end
196
+ Chook.logger.info handlers.empty? ? 'No general handlers found' : "Loaded #{handlers.values.flatten.size} general handlers for #{handlers.keys.size} event triggers"
197
+ else
198
+ Chook.logger.error "General handler directory '#{from_dir}' not a readable directory. No general handlers loaded. "
136
199
  end
137
200
 
138
- handler_dir.children.each do |handler_file|
139
- load_handler(handler_file) if handler_file.file? && handler_file.readable?
201
+ # Named Handlers
202
+ Chook.logger.info "#{load_type} named handlers from directory: #{named_handler_dir}"
203
+ if named_handler_dir.directory? && named_handler_dir.readable?
204
+ named_handler_dir.children.each do |handler_file|
205
+ load_named_handler(handler_file) if handler_file.file? && handler_file.readable?
206
+ end
207
+ Chook.logger.info "Loaded #{named_handlers.size} named handlers"
208
+ else
209
+ Chook.logger.error "Named handler directory '#{named_handler_dir}' not a readable directory. No named handlers loaded. "
140
210
  end
141
211
 
142
- Chook.logger.info "Loaded #{@handlers.values.flatten.size} handlers for #{@handlers.keys.size} event triggers"
143
- @loaded_handler = nil
212
+ @reloading = false
144
213
  end # load handlers
145
214
 
146
- # Load an event handler from a file.
147
- # Handler files must begin with the name of the event they handle,
215
+ # Load a general event handler from a file.
216
+ #
217
+ # General Handler files must begin with the name of the event they handle,
148
218
  # e.g. ComputerAdded, followed by: nothing, a dot, a dash, or
149
219
  # and underscore. Case doesn't matter.
150
220
  # So all of these are OK:
@@ -154,82 +224,112 @@ module Chook
154
224
  # Computeradded-update-ldap
155
225
  # There can be as many as desired for each event.
156
226
  #
157
- # Each must be either:
158
- # - An executable file, which will have the raw JSON from the JSS piped
159
- # to it's stdin when executed
160
- # or
161
- # - A non-executable file of ruby code like this:
162
- # Chook.event_handler do |event|
163
- # # your code goes here.
164
- # end
165
- #
166
- # (see the Chook README for details about writing the ruby handlers)
167
- #
168
- # @param from_file [Pathname] the file from which to load the handler
227
+ # @param handler_file [Pathname] the file from which to load the handler
169
228
  #
170
229
  # @return [void]
171
230
  #
172
- def self.load_handler(from_file)
173
- Chook.logger.debug "Starting load of handler file '#{from_file.basename}'"
174
- handler_file = Pathname.new from_file
231
+ def self.load_general_handler(handler_file)
232
+ Chook.logger.debug "Starting load of general handler file '#{handler_file.basename}'"
233
+
175
234
  event_name = event_name_from_handler_filename(handler_file)
176
235
  unless event_name
177
- Chook.logger.debug "Ignoring file '#{from_file.basename}'"
236
+ Chook.logger.debug "Ignoring general handler file '#{handler_file.basename}': Filename doesn't start with event name"
178
237
  return
179
238
  end
180
239
 
181
240
  # create an array for this event's handlers, if needed
182
- @handlers[event_name] ||= []
241
+ handlers[event_name] ||= []
242
+
243
+ # external? if so, its executable and we only care about its pathname
244
+ if handler_file.executable?
245
+ Chook.logger.info "Loading external general handler file '#{handler_file.basename}' for #{event_name} events"
246
+ handlers[event_name] << handler_file
247
+ return
248
+ end
249
+
250
+ # Internal, we store an object with a .handle method
251
+ Chook.logger.info "Loading internal general handler file '#{handler_file.basename}' for #{event_name} events"
252
+ load_internal_handler handler_file
253
+ handlers[event_name] << @loaded_handler if @loaded_handler
254
+
255
+ end # self.load_general_handler(handler_file)
256
+
257
+ # Load a named event handler from a file.
258
+ #
259
+ # Named Handler files can have any name, as they are called directly
260
+ # from a Jamf webhook via URL.
261
+ #
262
+ # @param handler_file [Pathname] the file from which to load the handler
263
+ #
264
+ # @return [void]
265
+ #
266
+ def self.load_named_handler(handler_file)
267
+ Chook.logger.debug "Starting load of named handler file '#{handler_file.basename}'"
183
268
 
184
- return if load_external_handler(handler_file, event_name)
269
+ # external? if so, its executable and we only care about its pathname
270
+ if handler_file.executable?
271
+ Chook.logger.info "Loading external named handler file '#{handler_file.basename}'"
272
+ named_handlers[handler_file.basename.to_s] = handler_file
273
+ return
274
+ end
185
275
 
186
- load_internal_handler(handler_file, event_name)
187
- end # self.load_handler(handler_file)
276
+ # Internal, we store an object with a .handle method
277
+ Chook.logger.info "Loading internal named handler file '#{handler_file.basename}'"
278
+ load_internal_handler handler_file
279
+ named_handlers[handler_file.basename.to_s] = @loaded_handler if @loaded_handler
280
+ end # self.load_general_handler(handler_file)
188
281
 
189
282
  # if the given file is executable, store it's path as a handler for the event
190
283
  #
284
+ # @return [Boolean] did we load an external handler?
191
285
  #
192
- def self.load_external_handler(handler_file, event_name)
286
+ def self.load_external_handler(handler_file, event_name, named)
193
287
  return false unless handler_file.executable?
194
288
 
195
- Chook.logger.info "Loading external handler file '#{handler_file.basename}' for #{event_name} events"
289
+ say_named = named ? 'named ' : ''
290
+ Chook.logger.info "Loading #{say_named}external handler file '#{handler_file.basename}' for #{event_name} events"
291
+
292
+ if named
293
+ named_handlers[event_name][handler_file.basename.to_s] = handler_file
294
+ else
295
+ # store the Pathname, we'll pipe JSON to it
296
+ handlers[event_name] << handler_file
297
+ end
196
298
 
197
- # store the Pathname, we'll pipe JSON to it
198
- @handlers[event_name] << handler_file
199
299
  true
200
300
  end
201
301
 
202
302
  # if a given path is not executable, try to load it as an internal handler
203
303
  #
304
+ # @param handler_file[Pathname] the handler file
305
+ #
306
+ # @return [Object] and anonymous object that has a .handle method
204
307
  #
205
- def self.load_internal_handler(handler_file, event_name)
308
+ def self.load_internal_handler(handler_file)
206
309
  # load the file. If written correctly, it will
207
310
  # put an anon. Object with a #handle method into @loaded_handler
208
- Chook.logger.info "Loading internal handler file '#{handler_file.basename}' for #{event_name} events"
209
-
210
311
  unless handler_file.read =~ INTERNAL_HANDLER_BLOCK_START_RE
211
- Chook.logger.error "Internal handler file '#{handler_file.basename}' missing event_handler block"
212
- return
312
+ Chook.logger.error "Internal handler file '#{handler_file}' missing event_handler block"
313
+ return nil
213
314
  end
214
315
 
215
316
  # reset @loaded_handler - the `load` call will refill it
216
317
  # see Chook.event_handler
217
318
  @loaded_handler = nil
319
+
218
320
  begin
219
321
  load handler_file.to_s
220
322
  raise '@loaded handler nil after loading file' unless @loaded_handler
221
323
  rescue => e
222
- Chook.logger.error "FAILED loading internal handler file '#{handler_file.basename}': #{e}"
324
+ Chook.logger.error "FAILED loading internal handler file '#{handler_file}': #{e}"
223
325
  return
224
326
  end
225
327
 
226
- # add a method to the object to get its filename
227
- @loaded_handler.define_singleton_method(:handler_file) { handler_file.basename.to_s }
228
-
229
- @handlers[event_name] << @loaded_handler
328
+ # add a method to the object to get its Pathname
329
+ @loaded_handler.define_singleton_method(:handler_file) { handler_file }
230
330
 
231
- Chook.logger.debug "Loaded internal handler file '#{handler_file.basename}'"
232
- @loaded_handler = nil
331
+ # return it
332
+ @loaded_handler
233
333
  end
234
334
 
235
335
  # Given a handler filename, return the event name it wants to handle
@@ -47,7 +47,7 @@ module Chook
47
47
  end
48
48
 
49
49
  def event_message(msg)
50
- "Event #{@event.object_id}: #{msg}"
50
+ "Event #{@event.id}: #{msg}"
51
51
  end
52
52
 
53
53
  def debug(msg)
@@ -29,6 +29,7 @@ require 'open-uri'
29
29
  require 'pathname'
30
30
  require 'logger'
31
31
  require 'English'
32
+ require 'securerandom'
32
33
 
33
34
  require 'chook/version'
34
35
  require 'chook/procs' # must load before configuration
@@ -71,6 +71,7 @@ module Chook
71
71
  end # self.run
72
72
 
73
73
  def self.prep_to_run
74
+ @start_time = Time.now
74
75
  log_level ||= Chook.config.log_level
75
76
  @log_level = Chook::Procs::STRING_TO_LOG_LEVEL.call log_level
76
77
 
@@ -94,6 +95,28 @@ module Chook
94
95
  Chook::HandledEvent::Handlers.load_handlers
95
96
  end # prep to run
96
97
 
98
+ def self.starttime
99
+ @start_time
100
+ end
101
+
102
+ def self.uptime
103
+ @start_time ? "#{humanize_secs(Time.now - @start_time)} ago" : 'Not Running'
104
+ end
105
+
106
+ # Very handy!
107
+ # lifted from
108
+ # http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails
109
+ #
110
+ def self.humanize_secs(secs)
111
+ [[60, :second], [60, :minute], [24, :hour], [7, :day], [52.179, :week], [1_000_000, :year]].map do |count, name|
112
+ next unless secs.positive?
113
+
114
+ secs, n = secs.divmod(count)
115
+ n = n.to_i
116
+ "#{n} #{n == 1 ? name : (name.to_s + 's')}"
117
+ end.compact.reverse.join(' ')
118
+ end
119
+
97
120
  end # class server
98
121
 
99
122
  end # module
@@ -22,6 +22,10 @@
22
22
  line-height: 1.6;
23
23
  }
24
24
 
25
+ #serverstats {
26
+ vertical-align: bottom;
27
+ }
28
+
25
29
  .def_pronunciation {
26
30
  font-style: italic;
27
31
  font-size: 1.2em;
@@ -88,44 +88,23 @@ function hide_handler_viewer() {
88
88
  }
89
89
 
90
90
  // show the handler editor with the selected handler code
91
- // handler = the path to the hander fle.
92
- function edit_handler(handler, type) {
93
- var code = '';
94
- var editing_filename = handler;
95
-
96
- // new handler
97
- if (handler == 'new_handler') {
98
- editing_filename = new_handler_filename();
99
- if (editing_filename == 'Name Already Taken') {
100
- code = editing_filename;
101
- }
102
- document.getElementById("handler_viewer").value = code;
103
-
104
- if (document.getElementById("add_handler_external_radio").checked) {
105
- type = 'external';
106
- } else {
107
- type = 'internal';
108
- }
109
-
110
- // existing handler
111
- } else {
112
- fetch_handler_code(handler) ;
113
- }
114
- var now_editing = editing_filename + ' (' + type + ')'
115
- document.getElementById("currently_viewing_filename").innerHTML = now_editing;
91
+ // handler = the basename of the hander fle.
92
+ function view_handler_code(handler_path, type) {
93
+ fetch_handler_code(handler_path) ;
94
+ document.getElementById("currently_viewing_filename").innerHTML = 'Viewing handler file: ' + handler_path + ' (' + type + ')';
116
95
  document.getElementById("handler_viewer_div").style.display = 'block';
117
96
  }
118
97
 
119
98
  // get the code for an existing handler into the editor
120
99
  function fetch_handler_code(handler) {
121
- var editor = document.getElementById("handler_viewer");
122
- var url = '/handler_code/' + handler
100
+ var viewer = document.getElementById("handler_viewer");
101
+ var url = '/handler_code?filepath=' + encodeURIComponent(handler)
123
102
  var xhttp = new XMLHttpRequest();
124
103
  xhttp.onreadystatechange = function() {
125
104
  if (this.readyState == 4 && this.status == 200) {
126
- editor.value = xhttp.responseText;
105
+ viewer.value = xhttp.responseText;
127
106
  } else {
128
- editor.value = 'ERROR: File Not Found';
107
+ viewer.value = 'ERROR: File Not Found';
129
108
  }
130
109
  };
131
110
  xhttp.open("GET", url, true);
@@ -49,6 +49,7 @@ end # Chook
49
49
 
50
50
  require 'chook/server/routes/home'
51
51
  require 'chook/server/routes/handle_webhook_event'
52
+ require 'chook/server/routes/handle_by_name'
52
53
  require 'chook/server/routes/handlers'
53
54
  require 'chook/server/routes/login_logout'
54
55
  require 'chook/server/routes/log'
@@ -0,0 +1,65 @@
1
+ ### Copyright 2017 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ module Chook
27
+
28
+ # see server.rb
29
+ class Server < Sinatra::Base
30
+
31
+ post '/handler/:handler_name' do
32
+ # enforce http basic auth if needed
33
+ protect_via_basic_auth!
34
+
35
+ # rewind to ensure read-pointer is at the start
36
+ request.body.rewind #
37
+ raw_json = request.body.read
38
+
39
+ event = Chook::HandledEvent.parse_event raw_json
40
+
41
+ if event.nil?
42
+ logger.error "Empty JSON from #{request.ip}"
43
+ result = 400
44
+ else
45
+
46
+ event.logger.debug "START From #{request.ip}, WebHook '#{event.webhook_name}' (id: #{event.webhook_id})"
47
+ event.logger.debug "Thread id: #{Thread.current.object_id}; JSON: #{raw_json}"
48
+
49
+ result = event.handle_by_name params[:handler_name]
50
+
51
+ event.logger.debug "END #{result}"
52
+ end
53
+
54
+ # this route shouldn't have a session expiration
55
+ # And when it does, the date format is wrong, and the
56
+ # JAMFSoftwareServerLog complains about it for every
57
+ # webhook sent.
58
+ env['rack.session.options'].delete :expire_after
59
+
60
+ result
61
+ end # post
62
+
63
+ end # class
64
+
65
+ end # module
@@ -42,12 +42,12 @@ module Chook
42
42
  result = 400
43
43
  else
44
44
 
45
- event.logger.info "START From #{request.ip}, WebHook '#{event.webhook_name}' (id: #{event.webhook_id})"
45
+ event.logger.debug "START From #{request.ip}, WebHook '#{event.webhook_name}' (id: #{event.webhook_id})"
46
46
  event.logger.debug "Thread id: #{Thread.current.object_id}; JSON: #{raw_json}"
47
47
 
48
48
  result = event.handle
49
49
 
50
- event.logger.info "END #{result}"
50
+ event.logger.debug "END #{result}"
51
51
  end
52
52
 
53
53
  # this route shouldn't have a session expiration
@@ -35,10 +35,12 @@ module Chook
35
35
  'Handlers reloaded'
36
36
  end # get /
37
37
 
38
- # used by javascript to fetch the content of a handler
39
- get '/handler_code/:file' do
40
- file = Chook.config.handler_dir + params[:file]
41
- if file.file?
38
+ # used by javascript to fetch the content of a handler file
39
+ get '/handler_code' do
40
+ file = Pathname.new params[:filepath]
41
+
42
+ # only if its a known handler path
43
+ if Chook::HandledEvent::Handlers.all_handler_paths.include?(file) && file.file?
42
44
  body file.read
43
45
  else
44
46
  404
@@ -39,13 +39,28 @@ module Chook
39
39
  file = handler
40
40
  type = :external
41
41
  else
42
- file = Pathname.new(Chook.config.handler_dir) + handler.handler_file
42
+ file = handler.handler_file
43
43
  type = :internal
44
44
  end # if else
45
45
  @handlers_for_admin_page << { event: eventname, file: file, type: type }
46
46
  end # handlers each
47
47
  end # Handlers.handlers.each
48
48
 
49
+ # a list of current named handlers for the admin page
50
+ @named_handlers_for_admin_page = []
51
+
52
+ Chook::HandledEvent::Handlers.named_handlers.each do |name, handler|
53
+ if handler.is_a? Pathname
54
+ file = handler
55
+ type = :external
56
+ else
57
+ file = handler.handler_file
58
+ type = :internal
59
+ end # if else
60
+ @named_handlers_for_admin_page << { file: file, type: type }
61
+ end # handlers each
62
+
63
+
49
64
  # the current config, for the admin page
50
65
  @config_text =
51
66
  if Chook::Configuration::DEFAULT_CONF_FILE.file?
@@ -5,33 +5,49 @@
5
5
  Hide
6
6
  &nbsp;&nbsp;&nbsp;&nbsp;
7
7
 
8
- Current Webhook Handlers (#{@handlers_for_admin_page.size})
9
-
10
- #handlers_div
11
-
12
- Handler Directory:
13
- %span.monospaced= Chook.config.handler_dir.to_s
14
- &nbsp;&nbsp;&nbsp;&nbsp;
8
+ Current Webhook Handlers (#{@handlers_for_admin_page.size + @named_handlers_for_admin_page.size})
15
9
 
16
10
  %button#reload_all_handlers_btn{ type: 'button', onClick: 'reload_handlers();', title: 'reload all handlers' }
17
- Reload
11
+ Reload All
18
12
  &nbsp;&nbsp;
19
13
  %span#reloaded_notification
20
14
 
15
+ #handlers_div
16
+ General Handler Directory:
17
+ %span.monospaced= Chook.config.handler_dir.to_s
18
+ &nbsp;&nbsp;&nbsp;&nbsp;
19
+
21
20
  %table#handlers_table
22
21
  %tr#handlers_table_header_row
23
- %th.handlers_table_cell Event
24
- %th.handlers_table_cell{ width: '10%' } Handler Type
25
22
  %th.handlers_table_cell File Name
23
+ %th.handlers_table_cell{ width: '10%' } Handler Type
26
24
  %th.handlers_table_cell Actions
27
25
 
28
26
  - @handlers_for_admin_page.each do |hndlr_info|
29
27
  %tr
30
- %td.handlers_table_cell= hndlr_info[:event]
28
+ %td.handlers_table_cell= hndlr_info[:file].basename.to_s
31
29
  %td.handlers_table_cell= hndlr_info[:type].to_s
30
+ %td.handlers_table_cell
31
+ %button.edit_handler_btn{ type: 'button', onClick: "view_handler_code('#{hndlr_info[:file]}', '#{hndlr_info[:type]}');", title: 'View this handler' }
32
+ View
33
+ %br
34
+ %br
35
+ Named Handler Directory:
36
+ %span.monospaced= Chook.config.handler_dir.to_s + "/#{Chook::HandledEvent::Handlers::NAMED_HANDLER_SUBDIR}"
37
+ &nbsp;&nbsp;&nbsp;&nbsp;
38
+
39
+ %table#handlers_table
40
+ %tr#handlers_table_header_row
41
+ %th.handlers_table_cell File Name
42
+ %th.handlers_table_cell{ width: '10%' } Handler Type
43
+ %th.handlers_table_cell Actions
44
+
45
+ - @named_handlers_for_admin_page.each do |hndlr_info|
46
+ %tr
32
47
  %td.handlers_table_cell= hndlr_info[:file].basename.to_s
48
+ %td.handlers_table_cell= hndlr_info[:type].to_s
33
49
  %td.handlers_table_cell
34
- %button.edit_handler_btn{ type: 'button', onClick: "edit_handler('#{hndlr_info[:file].basename}', '#{hndlr_info[:type]}');", title: 'View this handler' }
50
+ %button.edit_handler_btn{ type: 'button', onClick: "view_handler_code('#{hndlr_info[:file]}', '#{hndlr_info[:type]}');", title: 'View this handler' }
35
51
  View
36
52
 
37
53
  #handler_viewer_div
@@ -41,7 +57,6 @@
41
57
  %button#hide_handler_viewer_btn{ type: 'button', onClick: 'hide_handler_viewer();', title: 'hide the handler editor' }
42
58
  Hide
43
59
  &nbsp;&nbsp;&nbsp;&nbsp;
44
- Viewing handler: &nbsp;&nbsp;
45
60
  %span.monospaced#currently_viewing_filename -nothing-
46
61
 
47
62
 
@@ -34,6 +34,11 @@
34
34
  %span.def_dialect Australian/NZ informal
35
35
  %br/
36
36
  %span.def_definition a chicken or fowl
37
+ %td#serverstats{ valign: 'bottom' }
38
+ Server started: #{Chook::Server.starttime.strftime '%Y-%m-%d %H:%M:%S'}
39
+ %br/
40
+ (#{Chook::Server.uptime})
41
+
37
42
  %hr/
38
43
  - if Chook.config.admin_user
39
44
  #login_logout_div
@@ -5,8 +5,17 @@
5
5
  Hide
6
6
  &nbsp;&nbsp;&nbsp;&nbsp;
7
7
 
8
- The Live Chook Log
8
+ The Live Chook Log &nbsp;&nbsp;&nbsp;&nbsp;
9
9
 
10
+ Server Log Level:
11
+ %select#log_level_select{ onchange: 'change_log_level();',
12
+ title: 'changes here affect logging the server, not just your view' }
13
+
14
+ %option{ value: 'fatal', selected: Chook.logger.level == Logger::FATAL } fatal
15
+ %option{ value: 'error', selected: Chook.logger.level == Logger::ERROR } error
16
+ %option{ value: 'warn', selected: Chook.logger.level == Logger::WARN } warn
17
+ %option{ value: 'info', selected: Chook.logger.level == Logger::INFO } info
18
+ %option{ value: 'debug', selected: Chook.logger.level == Logger::DEBUG } debug
10
19
 
11
20
  #logbox_div
12
21
  #logbox_btns
@@ -19,14 +28,6 @@
19
28
 
20
29
  &nbsp;&nbsp;&nbsp;&nbsp;
21
30
 
22
- Server Log Level:
23
- %select#log_level_select{ onchange: 'change_log_level();',
24
- title: 'changes here affect logging the server, not just your view' }
25
31
 
26
- %option{ value: 'fatal', selected: Chook.logger.level == Logger::FATAL } fatal
27
- %option{ value: 'error', selected: Chook.logger.level == Logger::ERROR } error
28
- %option{ value: 'warn', selected: Chook.logger.level == Logger::WARN } warn
29
- %option{ value: 'info', selected: Chook.logger.level == Logger::INFO } info
30
- %option{ value: 'debug', selected: Chook.logger.level == Logger::DEBUG } debug
31
32
 
32
33
  %textarea.monospaced#logbox{ readonly: true, rows: 20 }
@@ -49,6 +49,11 @@ module Chook
49
49
  # Chook::HandledSubjects and Chook::TestSubjects
50
50
  COMPUTER = 'Computer'.freeze
51
51
 
52
+ # The name of the DeviceAddedtoDEP subject (a.k.a. 'event_object')
53
+ # as known to the JSS. Also the class name of such subjects in
54
+ # Chook::HandledSubjects and Chook::TestSubjects
55
+ DEP_DEVICE = 'DEPDevice'.freeze
56
+
52
57
  # The name of the JSS subject (a.k.a. 'event_object')
53
58
  # as known to the JSS. Also the class name of such subjects in
54
59
  # Chook::HandledSubjects and Chook::TestSubjects
@@ -64,6 +69,11 @@ module Chook
64
69
  # Chook::HandledSubjects and Chook::TestSubjects
65
70
  PATCH_SW_UPDATE = 'PatchSoftwareTitleUpdated'.freeze
66
71
 
72
+ # The name of the ComputerPolicyFinished subject (a.k.a. 'event_object')
73
+ # as known to the JSS. Also the class name of such subjects in
74
+ # Chook::HandledSubjects and Chook::TestSubjects
75
+ POLICY_FINISHED = 'PolicyFinished'.freeze
76
+
67
77
  # The name of the Push subject (a.k.a. 'event_object')
68
78
  # as known to the JSS. Also the class name of such subjects in
69
79
  # Chook::HandledSubjects and Chook::TestSubjects
@@ -140,3 +150,5 @@ require 'chook/subject/rest_api_operation'
140
150
  require 'chook/subject/scep_challenge'
141
151
  require 'chook/subject/smart_group'
142
152
  require 'chook/test_subjects'
153
+ require 'chook/subject/dep_device'
154
+ require 'chook/subject/policy_finished'
@@ -0,0 +1,81 @@
1
+ ### Copyright 2020 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+ Chook::Subject.classes[Chook::Subject::DEP_DEVICE] = {
26
+ assetTag: {
27
+ validation: String,
28
+ # randomizer: ,
29
+ # sampler: ,
30
+ # api_object_attribute:
31
+ },
32
+ description: {
33
+ validation: String,
34
+ randomizer: :word,
35
+ # sampler: ,
36
+ # api_object_attribute:
37
+ },
38
+ deviceAssignedDate: {
39
+ to_json: :to_jss_epoch,
40
+ validation: Time,
41
+ randomizer: :time,
42
+ # sampler: ,
43
+ # api_object_attribute:
44
+ },
45
+ deviceEnrollmentProgramInstanceId: {
46
+ validation: Integer,
47
+ randomizer: :int,
48
+ # sampler: ,
49
+ # api_object_attribute:
50
+ },
51
+ model: {
52
+ validation: String,
53
+ randomizer: [:computer_model, :mobile_model], # /:
54
+ # sampler: ,
55
+ api_object_attribute: [:hardware, :model]
56
+ },
57
+ serialNumber: {
58
+ validation: String, #:validate_serial_number,
59
+ # randomizer: :computer_serial_number,
60
+ # sampler: ,
61
+ api_object_attribute: :serial_number
62
+ }
63
+ }
64
+
65
+ # https://www.jamf.com/developers/webhooks/#deviceaddedtoDEP
66
+ # {
67
+ # "event": {
68
+ # "assetTag": "1664194",
69
+ # "description": "Mac Pro",
70
+ # "deviceAssignedDate": 1552478234000,
71
+ # "deviceEnrollmentProgramInstanceId": 1,
72
+ # "model": "Mac Pro",
73
+ # "serialNumber": "92D8014694C4BE96B3"
74
+ # },
75
+ # "webhook": {
76
+ # "eventTimestamp": 1553550275590,
77
+ # "id": 1,
78
+ # "name": "Webhook Documentation",
79
+ # "webhookEvent": "DeviceAddedToDEP"
80
+ # }
81
+ # }
@@ -0,0 +1,43 @@
1
+ ### Copyright 2017 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ # Add the attrbutes of a Computer subject to the Chook::Subject.attributes
27
+ # hash, to be used in defining Chook::TestSubjects::Computer and
28
+ # Chook::HandledSubjects::Computer
29
+ #
30
+ Chook::Subject.classes[Chook::Subject::POLICY_FINISHED] = {
31
+ computer: {
32
+ validation: Hash,
33
+ api_object_attribute: :computer
34
+ },
35
+ policyId: {
36
+ validation: Integer,
37
+ api_object_attribute: :policyId
38
+ },
39
+ successful: {
40
+ validation: :boolean,
41
+ api_object_attribute: :successful
42
+ }
43
+ }
@@ -27,6 +27,6 @@
27
27
  module Chook
28
28
 
29
29
  ### The version of the Chook framework
30
- VERSION = '1.1.2'.freeze
30
+ VERSION = '1.1.5b1'.freeze
31
31
 
32
32
  end # module
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.5b1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Lasell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-01-24 00:00:00.000000000 Z
12
+ date: 2020-12-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -87,6 +87,7 @@ files:
87
87
  - README.md
88
88
  - bin/chook-server
89
89
  - data/chook.conf.example
90
+ - data/com.pixar.chook-server.plist
90
91
  - data/sample_handlers/RestAPIOperation-executable
91
92
  - data/sample_handlers/RestAPIOperation.rb
92
93
  - data/sample_handlers/SmartGroupComputerMembershipChange-executable
@@ -131,6 +132,7 @@ files:
131
132
  - lib/chook/server/public/js/chook.js
132
133
  - lib/chook/server/public/js/logstream.js
133
134
  - lib/chook/server/routes.rb
135
+ - lib/chook/server/routes/handle_by_name.rb
134
136
  - lib/chook/server/routes/handle_webhook_event.rb
135
137
  - lib/chook/server/routes/handlers.rb
136
138
  - lib/chook/server/routes/home.rb
@@ -145,10 +147,12 @@ files:
145
147
  - lib/chook/server/views/sketch_admin
146
148
  - lib/chook/subject.rb
147
149
  - lib/chook/subject/computer.rb
150
+ - lib/chook/subject/dep_device.rb
148
151
  - lib/chook/subject/handled_subject.rb
149
152
  - lib/chook/subject/jss.rb
150
153
  - lib/chook/subject/mobile_device.rb
151
154
  - lib/chook/subject/patch_software_title_update.rb
155
+ - lib/chook/subject/policy_finished.rb
152
156
  - lib/chook/subject/push.rb
153
157
  - lib/chook/subject/randomizers.rb
154
158
  - lib/chook/subject/rest_api_operation.rb
@@ -175,12 +179,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
179
  version: '0'
176
180
  required_rubygems_version: !ruby/object:Gem::Requirement
177
181
  requirements:
178
- - - ">="
182
+ - - ">"
179
183
  - !ruby/object:Gem::Version
180
- version: '0'
184
+ version: 1.3.1
181
185
  requirements: []
182
- rubyforge_project:
183
- rubygems_version: 2.7.8
186
+ rubygems_version: 3.0.3
184
187
  signing_key:
185
188
  specification_version: 4
186
189
  summary: A Ruby framework for simulating and processing Jamf Pro Webhook Events