chook 1.0.0.b1

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +174 -0
  3. data/README.md +259 -0
  4. data/bin/chook-server +28 -0
  5. data/data/sample_handlers/RestAPIOperation-executable +91 -0
  6. data/data/sample_handlers/RestAPIOperation.rb +45 -0
  7. data/data/sample_handlers/SmartGroupComputerMembershipChange-executable +47 -0
  8. data/data/sample_handlers/SmartGroupComputerMembershipChange.rb +33 -0
  9. data/data/sample_jsons/ComputerAdded.json +27 -0
  10. data/data/sample_jsons/ComputerCheckIn.json +27 -0
  11. data/data/sample_jsons/ComputerInventoryCompleted.json +27 -0
  12. data/data/sample_jsons/ComputerPolicyFinished.json +27 -0
  13. data/data/sample_jsons/ComputerPushCapabilityChanged.json +27 -0
  14. data/data/sample_jsons/JSSShutdown.json +14 -0
  15. data/data/sample_jsons/JSSStartup.json +14 -0
  16. data/data/sample_jsons/MobileDeviceCheckIn.json +26 -0
  17. data/data/sample_jsons/MobileDeviceCommandCompleted.json +26 -0
  18. data/data/sample_jsons/MobileDeviceEnrolled.json +26 -0
  19. data/data/sample_jsons/MobileDevicePushSent.json +26 -0
  20. data/data/sample_jsons/MobileDeviceUnEnrolled.json +26 -0
  21. data/data/sample_jsons/PatchSoftwareTitleUpdated.json +14 -0
  22. data/data/sample_jsons/PushSent.json +11 -0
  23. data/data/sample_jsons/README +4 -0
  24. data/data/sample_jsons/RestAPIOperation.json +15 -0
  25. data/data/sample_jsons/SCEPChallenge.json +10 -0
  26. data/data/sample_jsons/SmartGroupComputerMembershipChange.json +13 -0
  27. data/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +13 -0
  28. data/lib/chook.rb +38 -0
  29. data/lib/chook/configuration.rb +198 -0
  30. data/lib/chook/event.rb +153 -0
  31. data/lib/chook/event/handled_event.rb +154 -0
  32. data/lib/chook/event/handled_event/handlers.rb +206 -0
  33. data/lib/chook/event/test_event.rb +140 -0
  34. data/lib/chook/event_handling.rb +40 -0
  35. data/lib/chook/event_testing.rb +43 -0
  36. data/lib/chook/foundation.rb +33 -0
  37. data/lib/chook/handled_events.rb +33 -0
  38. data/lib/chook/handled_subjects.rb +33 -0
  39. data/lib/chook/procs.rb +46 -0
  40. data/lib/chook/server.rb +121 -0
  41. data/lib/chook/server/routes.rb +27 -0
  42. data/lib/chook/server/routes/handle_webhook_event.rb +39 -0
  43. data/lib/chook/server/routes/home.rb +37 -0
  44. data/lib/chook/subject.rb +143 -0
  45. data/lib/chook/subject/computer.rb +121 -0
  46. data/lib/chook/subject/handled_subject.rb +84 -0
  47. data/lib/chook/subject/jss.rb +56 -0
  48. data/lib/chook/subject/mobile_device.rb +115 -0
  49. data/lib/chook/subject/patch_software_title_update.rb +55 -0
  50. data/lib/chook/subject/push.rb +38 -0
  51. data/lib/chook/subject/randomizers.rb +506 -0
  52. data/lib/chook/subject/rest_api_operation.rb +62 -0
  53. data/lib/chook/subject/samplers.rb +360 -0
  54. data/lib/chook/subject/scep_challenge.rb +32 -0
  55. data/lib/chook/subject/smart_group.rb +50 -0
  56. data/lib/chook/subject/test_subject.rb +195 -0
  57. data/lib/chook/subject/validators.rb +117 -0
  58. data/lib/chook/test_events.rb +33 -0
  59. data/lib/chook/test_subjects.rb +33 -0
  60. data/lib/chook/version.rb +32 -0
  61. metadata +129 -0
@@ -0,0 +1,154 @@
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
+ require 'chook/event/handled_event/handlers'
27
+
28
+ #
29
+ module Chook
30
+
31
+ # Load sample JSON files, one per event type
32
+ @sample_jsons = {}
33
+ base_dir = Pathname.new(__FILE__)
34
+ data_dir = base_dir.parent.parent.parent.parent
35
+ sample_json_dir = Pathname.new(data_dir.to_s + '/data/sample_jsons')
36
+ sample_json_dir.children.each do |jf|
37
+ event = jf.basename.to_s.chomp(jf.extname).to_sym
38
+ @sample_jsons[event] = jf.read
39
+ end
40
+
41
+ def self.sample_jsons
42
+ @sample_jsons
43
+ end
44
+
45
+ # An event that has been recieved and needs to be handled.
46
+ #
47
+ # This is the parent class to all of the classes in the
48
+ # Chook::HandledEvents module, which are dynamically defined when this
49
+ # file is loaded.
50
+ #
51
+ # All constants, methods, and attributes that are common to HandledEvent
52
+ # classes are defined here, including the interaction with the Handlers
53
+ # module.
54
+ #
55
+ # Subclasses are automatically generated from the keys and values of
56
+ # Chook::Event::EVENTS
57
+ #
58
+ # Each subclass will have a constant SUBJECT_CLASS containing the
59
+ # class of their #subject attribute.
60
+ #
61
+ class HandledEvent < Chook::Event
62
+
63
+ #### Class Methods
64
+
65
+ # Given some raw_json from the jss, create and return the correct
66
+ # HandledEvent subclass
67
+
68
+ # For each event type in Chook::Event::EVENTS
69
+ # generate a class for it, set its SUBJECT_CLASS constant
70
+ # and add it to the HandledEvents module.
71
+ #
72
+ # @return [void]
73
+ #
74
+ def self.generate_classes
75
+ Chook::Event::EVENTS.each do |class_name, subject|
76
+ next if Chook::HandledEvents.const_defined? class_name
77
+
78
+ # make the new HandledEvent subclass
79
+ the_class = Class.new(Chook::HandledEvent)
80
+
81
+ # Set its EVENT_NAME constant, which is used
82
+ # for finding it's handlers, among other things.
83
+ the_class.const_set Chook::Event::EVENT_NAME_CONST, class_name
84
+
85
+ # Set its SUBJECT_CLASS constant to the appropriate
86
+ # class in the HandledSubjects module.
87
+ the_class.const_set Chook::Event::SUBJECT_CLASS_CONST, Chook::HandledSubjects.const_get(subject)
88
+
89
+ # Add the new class to the HandledEvents module.
90
+ Chook::HandledEvents.const_set(class_name, the_class)
91
+ end # each classname, subject
92
+ end # self.generate_classes
93
+
94
+ # Given the raw json from the JSS webhook,
95
+ # create an object of the correct Event subclass
96
+ #
97
+ # @param [String] raw_event_json The JSON http POST content from the JSS
98
+ #
99
+ # @return [JSSWebHooks::Event subclass] the Event subclass matching the event
100
+ #
101
+ def self.parse_event(raw_event_json)
102
+ event_json = JSON.parse(raw_event_json, symbolize_names: true)
103
+ event_name = event_json[:webhook][:webhookEvent]
104
+ Chook::HandledEvents.const_get(event_name).new raw_event_json
105
+ end
106
+
107
+ #### Attributes
108
+
109
+ # @return [Array<Proc,Pathname>] the handlers defined for this event.
110
+ # Each is either a proc, in which case it is called with this
111
+ # instance as its sole paramter, or its a Pathname to an executable
112
+ # file, in which case the @raw_json is passed to its stdin.
113
+ # See the Chook::HandledEvent::Handlers module.
114
+ attr_reader :handlers
115
+
116
+ #### Constructor
117
+
118
+ # Handled Events are always built from raw_json.
119
+ #
120
+ def initialize(raw_event_json)
121
+ super raw_json: raw_event_json
122
+ end # init
123
+
124
+ def handle
125
+ handlers = Handlers.handlers[self.class.const_get(Chook::Event::EVENT_NAME_CONST)]
126
+ handlers.each do |handler|
127
+ case handler
128
+ when Pathname
129
+ pipe_to_executable handler
130
+ when Proc
131
+ handle_with_proc handler
132
+ end # case
133
+ end # @handlers.each do |handler|
134
+
135
+ # the handle method should return a string,
136
+ # which is the body of the HTTP result for
137
+ # POSTing the event
138
+ "Processed by #{handlers.count} handlers\n"
139
+ end # def handle
140
+
141
+ # TODO: Add something here that cleans up old threads and forks
142
+ def pipe_to_executable(handler)
143
+ _thread = Thread.new do
144
+ IO.popen([handler.to_s], 'w') { |h| h.puts @raw_json }
145
+ end
146
+ end
147
+
148
+ def handle_with_proc(handler)
149
+ _thread = Thread.new { handler.call self }
150
+ end
151
+
152
+ end # class HandledEvent
153
+
154
+ end # module
@@ -0,0 +1,206 @@
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
+ module Chook
26
+
27
+ # This method is used by the Ruby event-handler files.
28
+ #
29
+ # Loading them should call this method and pass in a block
30
+ # with one parameter: a Chook::HandledEvent subclass instance.
31
+ #
32
+ # The block is then converted to a Proc instance in @loaded_event_handler
33
+ # and from there can be stored for use by the event identified by the filename.
34
+ #
35
+ # NOTE: the files should be read with 'load' not 'require', so that they can
36
+ # be re-loaded as needed
37
+ #
38
+ # @see_also Chook::load_handlers
39
+ #
40
+ # @param [Block] block the block to be used as an event handler
41
+ #
42
+ # @yieldparam [JSS::WebHooks::Event subclass] The event to be handled
43
+ #
44
+ # @return [Proc] the block converted to a Proc
45
+ #
46
+ def self.event_handler(&block)
47
+ HandledEvent::Handlers.loaded_handler = Proc.new(&block)
48
+ end
49
+
50
+ # the server class
51
+ class HandledEvent < Event
52
+
53
+ # The Handlers namespace module
54
+ module Handlers
55
+
56
+ # Module Constants
57
+ ############################
58
+
59
+ DEFAULT_HANDLER_DIR = '/Library/Application Support/Chook'.freeze
60
+
61
+ # Module Instance Variables, & accessors
62
+ ############################
63
+
64
+ # This holds the most recently loaded Proc handler
65
+ # until it can be stored in the @handlers Hash
66
+ @loaded_handler = nil
67
+
68
+ # Getter for @loaded_handler
69
+ #
70
+ # @return [Proc,nil] the most recent Proc loaded from a handler file.
71
+ # destined for storage in @handlers
72
+ #
73
+ def self.loaded_handler
74
+ @loaded_handler
75
+ end
76
+
77
+ # Setter for @loaded_event_handler
78
+ #
79
+ # @param a_proc [Proc] a Proc object for storage in @handlers
80
+ #
81
+ def self.loaded_handler=(a_proc)
82
+ @loaded_handler = a_proc
83
+ end
84
+
85
+ # A hash of loaded handlers.
86
+ # Keys are Strings - the name of the events handled
87
+ # Values are Arrays of either Procs, or Pathnames to executable files.
88
+ # See the .handlers getter Methods
89
+ @handlers = {}
90
+
91
+ # Getter for @event_handlers
92
+ #
93
+ # @return [Hash{String => Array}] a mapping of Event Names as the come from
94
+ # the JSS to an Array of handlers for the event. The handlers are either
95
+ # Proc objects to call from within ruby, or Pathnames to executable files
96
+ # which will take raw JSON on stdin.
97
+ def self.handlers
98
+ @handlers
99
+ end
100
+
101
+ # Module Methods
102
+ ############################
103
+
104
+ # Load all the event handlers from the handler_dir or an arbitrary dir.
105
+ #
106
+ # @param from_dir [String, Pathname] directory from which to load the
107
+ # handlers. Defaults to CONFIG.handler_dir or DEFAULT_HANDLER_DIR if
108
+ # config is unset
109
+ #
110
+ # @param reload [Boolean] should we reload handlers if they've already
111
+ # been loaded?
112
+ #
113
+ # @return [void]
114
+ #
115
+ def self.load_handlers(from_dir: Chook::CONFIG.handler_dir, reload: false)
116
+ from_dir ||= DEFAULT_HANDLER_DIR
117
+ if reload
118
+ @handlers_loaded_from = nil
119
+ @handlers = {}
120
+ @loaded_handler = nil
121
+ end
122
+
123
+ handler_dir = Pathname.new(from_dir)
124
+ return unless handler_dir.directory? && handler_dir.readable?
125
+
126
+ handler_dir.children.each do |handler_file|
127
+ load_handler(handler_file) if handler_file.file? && handler_file.readable?
128
+ end
129
+
130
+ @handlers_loaded_from = handler_dir
131
+ @handlers.values.flatten.size
132
+ end # load handlers
133
+
134
+ # Load an event handler from a file.
135
+ # Handler files must begin with the name of the event they handle,
136
+ # e.g. ComputerAdded, followed by: nothing, a dot, a dash, or
137
+ # and underscore. Case doesn't matter.
138
+ # So all of these are OK:
139
+ # ComputerAdded
140
+ # computeradded.sh
141
+ # COMPUTERAdded_notify_team
142
+ # Computeradded-update-ldap
143
+ # There can be as many as desired for each event.
144
+ #
145
+ # Each must be either:
146
+ # - An executable file, which will have the raw JSON from the JSS piped
147
+ # to it's stdin when executed
148
+ # or
149
+ # - A non-executable file of ruby code like this:
150
+ # Chook.event_handler do |event|
151
+ # # your code goes here.
152
+ # end
153
+ #
154
+ # (see the Chook README for details about writing the ruby handlers)
155
+ #
156
+ # @param from_file [Pathname] the file from which to load the handler
157
+ #
158
+ # @return [void]
159
+ #
160
+ def self.load_handler(from_file)
161
+ handler_file = Pathname.new from_file
162
+ event_name = event_name_from_handler_filename(handler_file)
163
+ return unless event_name
164
+
165
+ # create an array for this event's handlers, if needed
166
+ @handlers[event_name] ||= []
167
+
168
+ if handler_file.executable?
169
+ # store as a Pathname, we'll pipe JSON to it
170
+ unless @handlers[event_name].include? handler_file
171
+ @handlers[event_name] << handler_file
172
+ puts "===> Loaded executable handler file '#{handler_file.basename}'"
173
+ end
174
+ return
175
+ end
176
+
177
+ # load the file. If written correctly, it will
178
+ # put a Proc into @loaded_handler
179
+ load handler_file.to_s
180
+ if @loaded_handler
181
+ @handlers[event_name] << @loaded_handler
182
+ puts "===> Loaded internal handler file '#{handler_file.basename}'"
183
+ @loaded_handler = nil
184
+ else
185
+ puts "===> FAILED loading internal handler file '#{handler_file.basename}'"
186
+ end
187
+ end # self.load_handler(handler_file)
188
+
189
+ # Given a handler filename, return the event name it wants to handle
190
+ #
191
+ # @param [Pathname] filename The filename from which to glean the
192
+ # event name.
193
+ #
194
+ # @return [String,nil] The matching event name or nil if no match
195
+ #
196
+ def self.event_name_from_handler_filename(filename)
197
+ @event_names ||= Chook::Event::EVENTS.keys
198
+ desired_event_name = filename.basename.to_s.split(/\.|-|_/).first
199
+ @event_names.select { |n| desired_event_name.casecmp(n).zero? }.first
200
+ end
201
+
202
+ end # module Handler
203
+
204
+ end # class handledevent
205
+
206
+ end # module
@@ -0,0 +1,140 @@
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
+ # An event that will be sent to a Webhook Server, simulating
29
+ # one from the JSS.
30
+ #
31
+ # This is the parent class to all of the classes in the
32
+ # Chook::TestEvents module, which are dynamically defined when this
33
+ # file is loaded.
34
+ #
35
+ # All constants, methods, and attributes that are common to TestEvent
36
+ # classes are defined here.
37
+ #
38
+ class TestEvent < Chook::Event
39
+
40
+ EVENT_ATTRIBUTES = %w(webhook_id webhook_name subject).freeze
41
+
42
+ # For each event type in Chook::Event::EVENTS.keys
43
+ # generate a TestEvent class for it, set its SUBJECT_CLASS constant
44
+ # and add it to the TestEvents module.
45
+ #
46
+ # @return [void]
47
+ #
48
+ def self.generate_classes
49
+ Chook::Event::EVENTS.each do |classname, subject|
50
+ next if Chook::TestEvents.const_defined? classname
51
+ # make the new TestEvent subclass
52
+ new_class = Class.new(Chook::TestEvent) do
53
+ # Setters & Getters
54
+ EVENT_ATTRIBUTES.each do |attribute|
55
+ # Getter
56
+ attr_reader attribute
57
+ # Setter
58
+ if attribute == 'subject'
59
+ define_method("#{attribute}=") do |new_val|
60
+ raise "Invalid TestSubject: Chook::TestEvents::#{classname} requires a Chook::TestSubjects::#{EVENTS[classname]}" unless Chook::Validators.send(:valid_test_subject, classname, new_val)
61
+ instance_variable_set(('@' + attribute.to_s), new_val)
62
+ end # end define_method
63
+ else
64
+ define_method("#{attribute}=") do |new_val|
65
+ instance_variable_set(('@' + attribute.to_s), new_val)
66
+ end # end define_method
67
+ end
68
+ end # end EVENT_ATTRIBUTES.each do |attribute|
69
+ end # end new_class
70
+
71
+ # Set its EVENT_NAME constant
72
+ new_class.const_set Chook::TestEvent::EVENT_NAME_CONST, classname
73
+
74
+ # Set its SUBJECT_CLASS constant to the appropriate
75
+ # class in the TestEvents module.
76
+ new_class.const_set Chook::TestEvent::SUBJECT_CLASS_CONST, Chook::TestSubjects.const_get(subject)
77
+
78
+ # Add the new class to the HandledEvents module.
79
+ Chook::TestEvents.const_set(classname, new_class)
80
+ end # each classname, subject
81
+ end # self.generate_classes
82
+
83
+ # json_hash
84
+ # Used by the fire method
85
+ #
86
+ # @return [Hash] A JSON Event payload formatted as a Hash.
87
+ #
88
+ def json_hash
89
+ raw_hash_form = {}
90
+ raw_hash_form['webhook'.to_sym] = { 'webhookEvent'.to_sym => self.class.to_s.split('::')[-1] }
91
+ EVENT_ATTRIBUTES.each do |json_attribute|
92
+ next if json_attribute.include? 'json'
93
+ if json_attribute == 'subject'
94
+ raw_hash_form['event'.to_sym] = instance_variable_get('@' + json_attribute).json_hash
95
+ else
96
+ json_hash_attribute = json_attribute.split('webhook_')[1] || json_attribute
97
+ nested_hash = Hash.new do |hash, key|
98
+ hash[key] = {}
99
+ end # end nested_hash
100
+ nested_hash[json_hash_attribute.to_sym] = instance_variable_get('@' + json_attribute)
101
+ raw_hash_form['webhook'.to_sym][nested_hash.keys[0]] = nested_hash[nested_hash.keys[0]]
102
+ end
103
+ end # end EVENT_ATTRIBUTES.each do |json_attribute|
104
+ raw_hash_form # This is the structural equivalent of the Chook::Event @json_hash form
105
+ end # end json_hash
106
+
107
+ # fire
108
+ #
109
+ # @param [String] server_url The URL of a server that can handle an Event
110
+ # @return [void]
111
+ #
112
+ def fire(server_url)
113
+ raise 'Please provide a destination server URL' unless server_url
114
+ uri = URI.parse(server_url)
115
+ raise 'Please provide a valid destination server URL' if uri.host.nil?
116
+ data = json_hash.to_json # This is the structural equivalent of the Chook::Event @raw_json form
117
+ http_connection = Net::HTTP.new uri.host, uri.port
118
+ http_connection.post(uri, data)
119
+ end # end fire
120
+
121
+ # initialize
122
+ # The optional argument is a Hash with the appropriate keys defiend
123
+ #
124
+ # @param [Hash] event_data nil or Hash
125
+ # @return [TestEvent] A new TestEvents subclass object
126
+ #
127
+ def initialize(event_data = nil)
128
+ if event_data
129
+ event_data.each do |key, value|
130
+ next unless EVENT_ATTRIBUTES.include? key
131
+ instance_variable_set(('@' + key.to_s), value)
132
+ end # event_data.each
133
+ else
134
+ EVENT_ATTRIBUTES.each { |attribute| instance_variable_set(('@' + attribute.to_s), nil) }
135
+ end # end if event_data
136
+ end # end init
137
+
138
+ end # class TestEvent
139
+
140
+ end # module