ruby-jss 0.11.0 → 0.12.0

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.

Potentially problematic release.


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

@@ -0,0 +1,132 @@
1
+ ### Copyright 2018 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
+ ###
27
+ module JSS
28
+
29
+ #
30
+ class Client
31
+
32
+ # Constants
33
+ #####################################
34
+
35
+ # The Pathname to the jamf binary executable
36
+ # Before SIP (macOS 10.10 and below)
37
+ ORIG_JAMF_BINARY = Pathname.new '/usr/sbin/jamf'
38
+
39
+ # The Pathname to the jamf binary executable
40
+ # After SIP (OS X 10.11 and above)
41
+ SIP_JAMF_BINARY = USR_LOCAL_BIN_FOLDER + 'jamf'
42
+
43
+ # The path to the jamf binary
44
+ JAMF_BINARY = SIP_JAMF_BINARY.executable? ? SIP_JAMF_BINARY : ORIG_JAMF_BINARY
45
+
46
+ # These jamf commands don't need root privs (most do)
47
+ ROOTLESS_JAMF_COMMANDS = %i[
48
+ about
49
+ checkJSSConnection
50
+ getARDFields
51
+ getComputerName
52
+ help
53
+ listUsers
54
+ version
55
+ ].freeze
56
+
57
+ # the option that makes the jamf binary verbose
58
+ JAMF_VERBOSE_OPT = ' -verbose'.freeze
59
+
60
+ # class Methods
61
+ #####################################
62
+
63
+ # Run an arbitrary jamf binary command.
64
+ #
65
+ # @note Most jamf commands require superuser/root privileges.
66
+ #
67
+ # @param command[String,Symbol] the jamf binary command to run
68
+ # The command is the single jamf command that comes after the/usr/bin/jamf.
69
+ #
70
+ # @param args[String,Array] the arguments passed to the jamf command.
71
+ # This is to be passed to Kernel.` (backtick), after being combined with the
72
+ # jamf binary and the jamf command
73
+ #
74
+ # @param verbose[Boolean] Should the stdout & stderr of the jamf binary be sent to
75
+ # the current stdout in realtime, as well as returned as a string?
76
+ #
77
+ # @return [String] the stdout & stderr of the jamf binary.
78
+ #
79
+ # @example
80
+ # These two are equivalent:
81
+ #
82
+ # JSS::Client.run_jamf "recon", "-assetTag 12345 -department 'IT Support'"
83
+ #
84
+ # JSS::Client.run_jamf :recon, ['-assetTag', '12345', '-department', 'IT Support'"]
85
+ #
86
+ #
87
+ # The details of the Process::Status for the jamf binary process can be
88
+ # captured from $CHILD_STATUS immediately after calling. (See Process::Status)
89
+ #
90
+ def self.run_jamf(command, args = nil, verbose = false)
91
+ raise JSS::UnmanagedError, 'The jamf binary is not installed on this computer.' unless installed?
92
+ unless ROOTLESS_JAMF_COMMANDS.include?(command.to_sym) || JSS.superuser?
93
+ raise JSS::UnsupportedError, 'You must have root privileges to run that jamf binary command'
94
+ end
95
+ cmd = build_jamf_command command, args
96
+ cmd += " #{JAMF_VERBOSE_OPT}" if verbose && !cmd.include?(JAMF_VERBOSE_OPT)
97
+ execute_jamf cmd, verbose
98
+ end # run_jamf
99
+
100
+ private_class_method
101
+
102
+ def self.build_jamf_command(command, args)
103
+ case args
104
+ when nil
105
+ "#{JAMF_BINARY} #{command}"
106
+ when String
107
+ "#{JAMF_BINARY} #{command} #{args}"
108
+ when Array
109
+ ([JAMF_BINARY.to_s, command] + args).join(' ')
110
+ else
111
+ raise JSS::InvalidDataError, 'args must be a String or Array of Strings'
112
+ end # case
113
+ end
114
+
115
+ def self.execute_jamf(cmd, verbose)
116
+ puts "Running: #{cmd}" if verbose
117
+ output = ''
118
+ IO.popen("#{cmd} 2>&1") do |proc|
119
+ loop do
120
+ line = proc.gets
121
+ break unless line
122
+ output << line
123
+ puts line if verbose
124
+ end
125
+ end
126
+ output.force_encoding('UTF-8')
127
+ output
128
+ end
129
+
130
+ end # class Client
131
+
132
+ end # module
@@ -0,0 +1,298 @@
1
+ ### Copyright 2018 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
+ ###
27
+ module JSS
28
+
29
+ #
30
+ class Client
31
+
32
+ # Constants
33
+ #####################################
34
+
35
+ # The Pathname to the jamfHelper executable
36
+ JAMF_HELPER = SUPPORT_BIN_FOLDER + 'jamfHelper.app/Contents/MacOS/jamfHelper'
37
+
38
+ # The window_type options for jamfHelper
39
+ JAMF_HELPER_WINDOW_TYPES = {
40
+ hud: 'hud',
41
+ utility: 'utility',
42
+ util: 'utility',
43
+ full_screen: 'fs',
44
+ fs: 'fs'
45
+ }.freeze
46
+
47
+ # The possible window positions for jamfHelper
48
+ JAMF_HELPER_WINDOW_POSITIONS = [nil, :ul, :ll, :ur, :lr].freeze
49
+
50
+ # The available buttons in jamfHelper
51
+ JAMF_HELPER_BUTTONS = [1, 2].freeze
52
+
53
+ # The possible alignment positions in jamfHelper
54
+ JAMF_HELPER_ALIGNMENTS = %i[right left center justified natural].freeze
55
+
56
+ # class Methods
57
+ #####################################
58
+
59
+
60
+ # A wrapper for the jamfHelper command, which can display a window on the client machine.
61
+ #
62
+ # The first parameter must be a symbol defining what kind of window to display. The options are
63
+ # - :hud - creates an Apple "Heads Up Display" style window
64
+ # - :utility or :util - creates an Apple "Utility" style window
65
+ # - :fs or :full_screen or :fullscreen - creates a full screen window that restricts all user input
66
+ # WARNING: Remote access must be used to unlock machines in this mode
67
+ #
68
+ # The remaining options Hash can contain any of the options listed. See below for descriptions.
69
+ #
70
+ # The value returned is the Integer exitstatus/stdout (both are the same) of the jamfHelper command.
71
+ # The meanings of those integers are:
72
+ #
73
+ # - 0 - Button 1 was clicked
74
+ # - 1 - The Jamf Helper was unable to launch
75
+ # - 2 - Button 2 was clicked
76
+ # - 3 - Process was started as a launchd task
77
+ # - XX1 - Button 1 was clicked with a value of XX seconds selected in the drop-down
78
+ # - XX2 - Button 2 was clicked with a value of XX seconds selected in the drop-down
79
+ # - 239 - The exit button was clicked
80
+ # - 240 - The "ProductVersion" in sw_vers did not return 10.5.X, 10.6.X or 10.7.X
81
+ # - 243 - The window timed-out with no buttons on the screen
82
+ # - 250 - Bad "-windowType"
83
+ # - 254 - Cancel button was select with delay option present
84
+ # - 255 - No "-windowType"
85
+ #
86
+ # If the :abandon_process option is given, the integer returned is the Process ID
87
+ # of the abondoned process running jamfHelper.
88
+ #
89
+ # See also /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help
90
+ #
91
+ # @note the -startlaunchd and -kill options are not available in this implementation, since
92
+ # they don't work at the moment (casper 9.4).
93
+ # -startlaunchd seems to be required to NOT use launchd, and when it's ommited, an error is generated
94
+ # about the launchd plist permissions being incorrect.
95
+ #
96
+ # @param window_type[Symbol] The type of window to display
97
+ #
98
+ # @param opts[Hash] the options for the window
99
+ #
100
+ # @option opts :window_position [Symbol,nil] one of [ nil, :ul, :ll. :ur, :lr ]
101
+ # Positions window in the upper right, upper left, lower right or lower left of the user's screen
102
+ # If no input is given, the window defaults to the center of the screen
103
+ #
104
+ # @option opts :title [String]
105
+ # Sets the window's title to the specified string
106
+ #
107
+ # @option opts :heading [String]
108
+ # Sets the heading of the window to the specified string
109
+ #
110
+ # @option opts :align_heading [Symbol] one of [:right, :left, :center, :justified, :natural]
111
+ # Aligns the heading to the specified alignment
112
+ #
113
+ # @option opts :description [String]
114
+ # Sets the main contents of the window to the specified string
115
+ #
116
+ # @option opts :align_description [Symbol] one of [:right, :left, :center, :justified, :natural]
117
+ # Aligns the description to the specified alignment
118
+ #
119
+ # @option opts :icon [String,Pathname]
120
+ # Sets the windows image field to the image located at the specified path
121
+ #
122
+ # @option opts :icon_size [Integer]
123
+ # Changes the image frame to the specified pixel size
124
+ #
125
+ # @option opts :full_screen_icon [any value]
126
+ # Scales the "icon" to the full size of the window.
127
+ # Note: Only available in full screen mode
128
+ #
129
+ # @option opts :button1 [String]
130
+ # Creates a button with the specified label
131
+ #
132
+ # @option opts :button2 [String]
133
+ # Creates a second button with the specified label
134
+ #
135
+ # @option opts :default_button [Integer] either 1 or 2
136
+ # Sets the default button of the window to the specified button. The Default Button will respond to "return"
137
+ #
138
+ # @option opts :cancel_button [Integer] either 1 or 2
139
+ # Sets the cancel button of the window to the specified button. The Cancel Button will respond to "escape"
140
+ #
141
+ # @option opts :timeout [Integer]
142
+ # Causes the window to timeout after the specified amount of seconds
143
+ # Note: The timeout will cause the default button, button 1 or button 2 to be selected (in that order)
144
+ #
145
+ # @option opts :show_delay_options [String,Array<Integer>] A String of comma-separated Integers, or an Array of Integers.
146
+ # Enables the "Delay Options Mode". The window will display a dropdown with the values passed through the string
147
+ #
148
+ # @option opts :countdown [any value]
149
+ # Displays a string notifying the user when the window will time out
150
+ #
151
+ # @option opts :align_countdown [Symbol] one of [:right, :left, :center, :justified, :natural]
152
+ # Aligns the countdown to the specified alignment
153
+ #
154
+ # @option opts :lock_hud [Boolean]
155
+ # Removes the ability to exit the HUD by selecting the close button
156
+ #
157
+ # @option opts :abandon_process [Boolean] Abandon the jamfHelper process so that your code can exit.
158
+ # This is mostly used so that a policy can finish while a dialog is waiting
159
+ # (possibly forever) for user response. When true, the returned value is the
160
+ # process id of the abandoned jamfHelper process.
161
+ #
162
+ # @option opts :output_file [String, Pathname] Save the output of jamfHelper
163
+ # (the exit code) into this file. This is useful when using abandon_process.
164
+ # The output file can be examined later to see what happened. If this option
165
+ # is not provided, no output is saved.
166
+ #
167
+ # @option opts :arg_string [String] The jamfHelper commandline args as a single
168
+ # String, the way you'd specify them in a shell. This is appended to any
169
+ # Ruby options provided when calling the method. So calling:
170
+ # JSS::Client.jamf_helper :hud, title: 'This is a title', arg_string: '-heading "this is a heading"'
171
+ # will run
172
+ # jamfHelper -windowType hud -title 'this is a title' -heading "this is a heading"
173
+ # When using this, be careful not to specify the windowType, since it's generated
174
+ # by the first, required, parameter of this method.
175
+ #
176
+ # @return [Integer] the exit status of the jamfHelper command. See above.
177
+ #
178
+ def self.jamf_helper(window_type = :hud, opts = {})
179
+ raise JSS::UnmanagedError, 'The jamfHelper app is not installed properly on this computer.' unless JAMF_HELPER.executable?
180
+
181
+ unless JAMF_HELPER_WINDOW_TYPES.include? window_type
182
+ raise JSS::InvalidDataError, "The first parameter must be a window type, one of :#{JAMF_HELPER_WINDOW_TYPES.keys.join(', :')}."
183
+ end
184
+
185
+ # start building the arg array
186
+
187
+ args = ['-startlaunchd', '-windowType', JAMF_HELPER_WINDOW_TYPES[window_type]]
188
+
189
+ opts.keys.each do |opt|
190
+ case opt
191
+ when :window_position
192
+ raise JSS::InvalidDataError, ":window_position must be one of :#{JAMF_HELPER_WINDOW_POSITIONS.join(', :')}." unless \
193
+ JAMF_HELPER_WINDOW_POSITIONS.include? opts[opt].to_sym
194
+ args << '-windowPosition'
195
+ args << opts[opt].to_s
196
+
197
+ when :title
198
+ args << '-title'
199
+ args << opts[opt].to_s
200
+
201
+ when :heading
202
+ args << '-heading'
203
+ args << opts[opt].to_s
204
+
205
+ when :align_heading
206
+ raise JSS::InvalidDataError, ":align_heading must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
207
+ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
208
+ args << '-alignHeading'
209
+ args << opts[opt].to_s
210
+
211
+ when :description
212
+ args << '-description'
213
+ args << opts[opt].to_s
214
+
215
+ when :align_description
216
+ raise JSS::InvalidDataError, ":align_description must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
217
+ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
218
+ args << '-alignDescription'
219
+ args << opts[opt].to_s
220
+
221
+ when :icon
222
+ args << '-icon'
223
+ args << opts[opt].to_s
224
+
225
+ when :icon_size
226
+ args << '-iconSize'
227
+ args << opts[opt].to_s
228
+
229
+ when :full_screen_icon
230
+ args << '-fullScreenIcon'
231
+
232
+ when :button1
233
+ args << '-button1'
234
+ args << opts[opt].to_s
235
+
236
+ when :button2
237
+ args << '-button2'
238
+ args << opts[opt].to_s
239
+
240
+ when :default_button
241
+ raise JSS::InvalidDataError, ":default_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \
242
+ JAMF_HELPER_BUTTONS.include? opts[opt]
243
+ args << '-defaultButton'
244
+ args << opts[opt].to_s
245
+
246
+ when :cancel_button
247
+ raise JSS::InvalidDataError, ":cancel_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \
248
+ JAMF_HELPER_BUTTONS.include? opts[opt]
249
+ args << '-cancelButton'
250
+ args << opts[opt].to_s
251
+
252
+ when :timeout
253
+ args << '-timeout'
254
+ args << opts[opt].to_s
255
+
256
+ when :show_delay_options
257
+ args << '-showDelayOptions'
258
+ args << JSS.to_s_and_a(opts[opt])[:arrayform].join(', ')
259
+
260
+ when :countdown
261
+ args << '-countdown' if opts[opt]
262
+
263
+ when :align_countdown
264
+ raise JSS::InvalidDataError, ":align_countdown must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
265
+ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
266
+ args << '-alignCountdown'
267
+ args << opts[opt].to_s
268
+
269
+ when :lock_hud
270
+ args << '-lockHUD' if opts[opt]
271
+
272
+ end # case opt
273
+ end # each do opt
274
+
275
+ cmd = Shellwords.escape JAMF_HELPER.to_s
276
+ args.each { |arg| cmd << " #{Shellwords.escape arg}" }
277
+ cmd << " #{opts[:arg_string]}" if opts[:arg_string]
278
+ cmd << " > #{Shellwords.escape opts[:output_file]}" if opts[:output_file]
279
+
280
+ if opts[:abandon_process]
281
+ pid = Process.fork
282
+ if pid.nil?
283
+ # In child
284
+ exec cmd
285
+ else
286
+ # In parent
287
+ Process.detach(pid)
288
+ pid
289
+ end
290
+ else
291
+ system cmd
292
+ $CHILD_STATUS.exitstatus
293
+ end
294
+ end # def self.jamf_helper
295
+
296
+ end # class Client
297
+
298
+ end # module
@@ -0,0 +1,114 @@
1
+ ### Copyright 2018 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
+ ###
27
+ module JSS
28
+
29
+ #
30
+ class Client
31
+
32
+ # Constants
33
+ #####################################
34
+
35
+ # The Pathname to the Management Action executable
36
+ MGMT_ACTION = SUPPORT_BIN_FOLDER + 'Management Action.app/Contents/MacOS/Management Action'
37
+
38
+ # NC_ALERT_STYLE_FLAGS = 4432
39
+ # NCPREFS_DOMAIN = 'com.apple.ncprefs'.freeze
40
+ # MGMT_ACTION_BUNDLE_ID = 'com.jamfsoftware.Management-Action'.freeze
41
+ # HUP_NOTIF_CTR_CMD = '/usr/bin/killall sighup usernoted NotificationCenter'.freeze
42
+
43
+ # class Methods
44
+ #####################################
45
+
46
+ def self.management_action(msg, title: nil, subtitle: nil, delay: 0)
47
+ raise JSS::InvalidDataError, 'delay: must be a non-negative integer.' unless delay.is_a?(Integer) && delay > -1
48
+
49
+ cmd = Shellwords.escape MGMT_ACTION.to_s
50
+ cmd << " -message #{Shellwords.escape msg.to_s}"
51
+ cmd << " -title #{Shellwords.escape title.to_s}" if title
52
+ cmd << " -subtitle #{Shellwords.escape subtitle.to_s}" if subtitle
53
+ cmd << " -deliverydelay #{Shellwords.escape delay}" if delay > 0
54
+ `#{cmd} 2>&1`
55
+ end
56
+
57
+ # an alias of management_action
58
+ def self.nc_notify(msg, title: nil, subtitle: nil, delay: 0)
59
+ management_action(msg, title: title, subtitle: subtitle, delay: delay)
60
+ end
61
+
62
+ private_class_method
63
+
64
+ # Skipping all the force-alerts stuff until we figure out cleaner
65
+ # ways to do it in 10.13+
66
+ # The plan is to make the NotificationCenter notification always be an
67
+ # 'alert' (which stays visible til the user clicks) rather than a
68
+ # 'banner' (which vanishes in a few seconds), regardless of the user's
69
+ # setting in the NC prefs.
70
+
71
+ def self.force_alerts
72
+ orig_flags = {}
73
+ console_users.each do |user|
74
+ orig_flags[user] = set_mgmt_action_ncprefs_flags user, NC_ALERT_STYLE_FLAGS, hup: false
75
+ end
76
+ system HUP_NOTIF_CTR_CMD unless orig_flags.empty?
77
+ sleep 1
78
+ orig_flags
79
+ end
80
+
81
+ def self.restore_alerts(orig_flags)
82
+ orig_flags.each do |user, flags|
83
+ set_mgmt_action_ncprefs_flags user, flags, hup: false
84
+ end
85
+ system HUP_NOTIF_CTR_CMD unless orig_flags.empty?
86
+ end
87
+
88
+ # set the NotificationCenter option flags for a user
89
+ # flags = an integer.
90
+ #
91
+ # Doesn't seem to work in 10.13, so ignore this for now.
92
+ #
93
+ # @return [Integer] the original flags, or given flags if no originals.
94
+ #
95
+ def self.set_mgmt_action_ncprefs_flags(user, flags, hup: true)
96
+ plist = Pathname.new "/Users/#{user}/Library/Preferences/#{NCPREFS_DOMAIN}.plist"
97
+ prefs = JSS.parse_plist plist
98
+ mgmt_action_setting = prefs['apps'].select { |a| a['bundle-id'] == MGMT_ACTION_BUNDLE_ID }.first
99
+ if mgmt_action_setting
100
+ orig_flags = mgmt_action_setting['flags']
101
+ mgmt_action_setting['flags'] = flags
102
+ else
103
+ orig_flags = flags
104
+ prefs['apps'] << { 'bundle-id' => MGMT_ACTION_BUNDLE_ID, 'flags' => flags }
105
+ end
106
+ # system "/usr/bin/defaults write #{NCPREFS_DOMAIN} '#{prefs.to_plist}'"
107
+ plist.open('w') { |f| f.write prefs.to_plist }
108
+ system HUP_NOTIF_CTR_CMD if hup
109
+ orig_flags
110
+ end
111
+
112
+ end # class Client
113
+
114
+ end # module