ruby-jss 1.2.9 → 1.5.1

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +196 -1
  3. data/lib/jamf.rb +10 -3
  4. data/lib/jamf/api/abstract_classes/collection_resource.rb +329 -150
  5. data/lib/jamf/api/abstract_classes/generic_reference.rb +9 -1
  6. data/lib/jamf/api/abstract_classes/json_object.rb +107 -83
  7. data/lib/jamf/api/abstract_classes/prestage.rb +55 -30
  8. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +21 -0
  9. data/lib/jamf/api/abstract_classes/resource.rb +4 -4
  10. data/lib/jamf/api/abstract_classes/singleton_resource.rb +1 -1
  11. data/lib/jamf/api/connection.rb +20 -12
  12. data/lib/jamf/api/connection/api_error.rb +8 -8
  13. data/lib/jamf/api/connection/token.rb +36 -15
  14. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +14 -1
  15. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  16. data/lib/jamf/api/json_objects/device_enrollment_device_sync_state.rb +81 -0
  17. data/lib/jamf/api/json_objects/locale.rb +59 -0
  18. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  19. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  20. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  21. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  22. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  23. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  24. data/lib/jamf/api/mixins/change_log.rb +201 -51
  25. data/lib/jamf/api/mixins/filterable.rb +51 -0
  26. data/lib/jamf/api/mixins/pageable.rb +208 -0
  27. data/lib/jamf/api/mixins/sortable.rb +59 -0
  28. data/lib/jamf/api/resources/collection_resources/building.rb +19 -8
  29. data/lib/jamf/api/resources/collection_resources/category.rb +5 -3
  30. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +11 -4
  31. data/lib/jamf/api/resources/collection_resources/department.rb +1 -1
  32. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  33. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  34. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +24 -22
  35. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  36. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  37. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +14 -14
  38. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  39. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  40. data/lib/jamf/configuration.rb +7 -9
  41. data/lib/jamf/ruby_extensions.rb +1 -0
  42. data/lib/jamf/ruby_extensions/array.rb +1 -1
  43. data/lib/jamf/ruby_extensions/array/utils.rb +3 -3
  44. data/lib/jamf/ruby_extensions/dig.rb +52 -0
  45. data/lib/jamf/validate.rb +63 -24
  46. data/lib/jamf/version.rb +1 -1
  47. data/lib/jss.rb +4 -1
  48. data/lib/jss/api_connection.rb +110 -397
  49. data/lib/jss/api_object.rb +16 -13
  50. data/lib/jss/api_object/advanced_search.rb +27 -26
  51. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  52. data/lib/jss/api_object/categorizable.rb +1 -1
  53. data/lib/jss/api_object/computer.rb +5 -1
  54. data/lib/jss/api_object/configuration_profile.rb +34 -3
  55. data/lib/jss/api_object/directory_binding.rb +273 -0
  56. data/lib/jss/api_object/directory_binding_type.rb +96 -0
  57. data/lib/jss/api_object/directory_binding_type/active_directory.rb +539 -0
  58. data/lib/jss/api_object/directory_binding_type/admitmac.rb +594 -0
  59. data/lib/jss/api_object/directory_binding_type/centrify.rb +226 -0
  60. data/lib/jss/api_object/directory_binding_type/open_directory.rb +178 -0
  61. data/lib/jss/api_object/directory_binding_type/powerbroker_identity_services.rb +73 -0
  62. data/lib/jss/api_object/disk_encryption_configurations.rb +114 -0
  63. data/lib/jss/api_object/distribution_point.rb +97 -37
  64. data/lib/jss/api_object/dock_item.rb +143 -0
  65. data/lib/jss/api_object/ebook.rb +1 -2
  66. data/lib/jss/api_object/extendable.rb +68 -32
  67. data/lib/jss/api_object/extension_attribute.rb +4 -3
  68. data/lib/jss/api_object/group.rb +33 -2
  69. data/lib/jss/api_object/mac_application.rb +107 -8
  70. data/lib/jss/api_object/mobile_device.rb +3 -0
  71. data/lib/jss/api_object/mobile_device_application.rb +12 -0
  72. data/lib/jss/api_object/network_segment.rb +195 -70
  73. data/lib/jss/api_object/package.rb +105 -40
  74. data/lib/jss/api_object/patch_source.rb +10 -9
  75. data/lib/jss/api_object/policy.rb +491 -7
  76. data/lib/jss/api_object/printer.rb +446 -0
  77. data/lib/jss/api_object/scopable.rb +10 -15
  78. data/lib/jss/api_object/scopable/scope.rb +386 -71
  79. data/lib/jss/api_object/self_servable.rb +17 -9
  80. data/lib/jss/api_object/uploadable.rb +1 -1
  81. data/lib/jss/api_object/user.rb +42 -1
  82. data/lib/jss/api_object/vpp_account.rb +209 -0
  83. data/lib/jss/api_object/vppable.rb +169 -13
  84. data/lib/jss/composer.rb +1 -1
  85. data/lib/jss/exceptions.rb +3 -0
  86. data/lib/jss/server.rb +15 -0
  87. data/lib/jss/utility.rb +8 -22
  88. data/lib/jss/validate.rb +53 -10
  89. data/lib/jss/version.rb +1 -1
  90. metadata +50 -22
@@ -103,8 +103,8 @@ module Jamf
103
103
  # The Pathname to the machine-wide preferences
104
104
  GLOBAL_CONF_FILE = Pathname.new "/etc/#{CONF_FILENAME}"
105
105
 
106
- # The Pathname to the user-specific preferences plist
107
- USER_CONF_FILE = ENV['HOME'] ? Pathname.new("~/.#{CONF_FILENAME}").expand_path : nil
106
+ # The Pathname to the user-specific preferences plist if there is one
107
+ USER_CONF_FILE = Pathname.new("#{ENV['HOME']}/.#{CONF_FILENAME}")
108
108
 
109
109
  # The attribute keys we maintain, and the type they should be stored as
110
110
  CONF_KEYS = {
@@ -134,7 +134,6 @@ module Jamf
134
134
  # Constructor
135
135
  #####################################
136
136
 
137
-
138
137
  # Initialize!
139
138
  #
140
139
  def initialize
@@ -145,7 +144,6 @@ module Jamf
145
144
  # Public Instance Methods
146
145
  #####################################
147
146
 
148
-
149
147
  # Clear all values
150
148
  #
151
149
  # @return [void]
@@ -159,7 +157,7 @@ module Jamf
159
157
  # @return [void]
160
158
  #
161
159
  def read_global
162
- read GLOBAL_CONF_FILE if GLOBAL_CONF_FILE&.file? && GLOBAL_CONF_FILE.readable?
160
+ read GLOBAL_CONF_FILE if GLOBAL_CONF_FILE.file? && GLOBAL_CONF_FILE.readable?
163
161
  end
164
162
 
165
163
  # (Re)read the user prefs, if it exists.
@@ -167,7 +165,7 @@ module Jamf
167
165
  # @return [void]
168
166
  #
169
167
  def read_user
170
- read USER_CONF_FILE if USER_CONF_FILE&.file? && USER_CONF_FILE.readable?
168
+ read USER_CONF_FILE if USER_CONF_FILE.file? && USER_CONF_FILE.readable?
171
169
  end
172
170
 
173
171
  # Clear the settings and reload the prefs files, or another file if provided
@@ -196,9 +194,9 @@ module Jamf
196
194
  def save(file)
197
195
  path =
198
196
  case file
199
- when :global then GLOBAL_CONF_FILE
200
- when :user then USER_CONF_FILE
201
- else Pathname.new(file)
197
+ when :global then GLOBAL_CONF_FILE
198
+ when :user then USER_CONF_FILE
199
+ else Pathname.new(file)
202
200
  end
203
201
 
204
202
  raise Jamf::MissingDataError, "No HOME environment variable, can't write to user conf file." if path.nil?
@@ -34,3 +34,4 @@ require 'jamf/ruby_extensions/object.rb'
34
34
  require 'jamf/ruby_extensions/pathname.rb'
35
35
  require 'jamf/ruby_extensions/string.rb'
36
36
  require 'jamf/ruby_extensions/array.rb'
37
+ require 'jamf/ruby_extensions/dig.rb'
@@ -26,7 +26,7 @@
26
26
  require 'jamf/ruby_extensions/array/predicates'
27
27
  require 'jamf/ruby_extensions/array/utils'
28
28
 
29
- #
29
+ # an array
30
30
  class Array
31
31
 
32
32
  include JamfRubyExtensions::Array::Predicates
@@ -25,6 +25,7 @@ module JamfRubyExtensions
25
25
 
26
26
  module Array
27
27
 
28
+ # Useful monkey patches for Array
28
29
  module Utils
29
30
 
30
31
  # Fetch a string from an Array case-insensitively,
@@ -40,8 +41,7 @@ module JamfRubyExtensions
40
41
  # nil if it doesn't exist
41
42
  #
42
43
  def j_ci_fetch(somestring)
43
- # select { |s| s&.casecmp? somestring }.first
44
- each { |s| return s if s&.casecmp?(somestring) }
44
+ each { |s| return s if s.respond_to?(:casecmp?) && s.casecmp?(somestring) }
45
45
  nil
46
46
  end
47
47
 
@@ -52,7 +52,7 @@ module JamfRubyExtensions
52
52
  # @return [Boolean]
53
53
  #
54
54
  def j_ci_include?(somestring)
55
- any? { |s| s&.casecmp? somestring }
55
+ any? { |s| s.respond_to?(:casecmp?) && s.casecmp?(somestring) }
56
56
  end
57
57
 
58
58
  end # module
@@ -0,0 +1,52 @@
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
+ # Gratefully borrowed from https://github.com/Invoca/ruby_dig
25
+
26
+ # modulize monkey patches
27
+ module RubyDig
28
+
29
+ def dig(key, *rest)
30
+ value = self[key]
31
+ if value.nil? || rest.empty?
32
+ value
33
+ elsif value.respond_to?(:dig)
34
+ value.dig(*rest)
35
+ else
36
+ raise TypeError, "#{value.class} does not have #dig method"
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ if RUBY_VERSION < '2.3'
43
+
44
+ # arrays
45
+ class Array; include RubyDig; end
46
+
47
+ # hashes
48
+ class Hash; include RubyDig; end
49
+
50
+ # ostructs
51
+ class OpenStruct; include RubyDig; end
52
+ end
@@ -37,7 +37,7 @@ module Jamf
37
37
  module Validate
38
38
 
39
39
  # The regular expression that matches a valid MAC address.
40
- MAC_ADDR_RE = /^[a-f0-9]{2}(:[a-f0-9]{2}){5}$/i
40
+ MAC_ADDR_RE = /^[a-f0-9]{2}(:[a-f0-9]{2}){5}$/i.freeze
41
41
 
42
42
  # Validate the format and content of a MAC address
43
43
  #
@@ -50,6 +50,7 @@ module Jamf
50
50
  def self.mac_address(val, msg = nil)
51
51
  msg ||= "Not a valid MAC address: '#{val}'"
52
52
  raise Jamf::InvalidDataError, msg unless val =~ MAC_ADDR_RE
53
+
53
54
  val
54
55
  end
55
56
 
@@ -68,10 +69,11 @@ module Jamf
68
69
  ok = false unless parts.size == 4
69
70
  parts.each { |p| ok = false unless p.j_integer? && p.to_i < 256 && p.to_i >= 0 }
70
71
  raise Jamf::InvalidDataError, msg unless ok
72
+
71
73
  val
72
74
  end
73
75
 
74
- # Does a give JSONObject class have a given JSON attribute?
76
+ # Does a given JSONObject class have a given JSON attribute?
75
77
  #
76
78
  # @param klass [<JSONObject] A class descended from JSONObject
77
79
  #
@@ -83,9 +85,24 @@ module Jamf
83
85
  raise "#{klass} is not a descendent of JSONObject" unless klass < Jamf::JSONObject
84
86
 
85
87
  raise Jamf::NoSuchItemError, "No attribute #{attr_name} for class #{klass}" unless klass::OBJECT_MODEL.key? attrib
88
+
86
89
  attr_name
87
90
  end
88
91
 
92
+ # Does a value exist in a given enum array?
93
+ #
94
+ # @param klass [<JSONObject] A class descended from JSONObject
95
+ #
96
+ # @param attr_name [Symbol] The attribute to validate
97
+ #
98
+ # @return [Symbol] The valid attribute
99
+ #
100
+ def self.in_enum(val, enum)
101
+ raise Jamf::InvalidDataError, "Value must be one of: #{enum.join ', '}" unless enum.include? val
102
+
103
+ val
104
+ end
105
+
89
106
  # Validate that a value doesn't already exist for a given identifier of
90
107
  # a given CollectionResource class
91
108
  #
@@ -116,14 +133,12 @@ module Jamf
116
133
  raise Jamf::AlreadyExistsError, msg
117
134
  end
118
135
 
136
+ TRUE_FALSE = [true, false].freeze
137
+
119
138
  # Confirm that the given value is a boolean value, accepting
120
- # Strings and Symbols, returning real booleans as needed
121
- #
122
- # Accepted True values: true, 'true', :true, 'yes', :yes
123
- #
124
- # Accepted False values: false, 'false', :false, 'no', :no
125
- #
126
- # all Strings and Symbols are case insensitive
139
+ # strings and symbols and returning real booleans as needed
140
+ # Accepts: true, false, 'true', 'false', 'yes', 'no', 't','f', 'y', or 'n'
141
+ # as strings or symbols, case insensitive
127
142
  #
128
143
  # @param val [Boolean,String,Symbol] The value to validate
129
144
  #
@@ -131,14 +146,36 @@ module Jamf
131
146
  #
132
147
  # @return [Boolean] the valid boolean
133
148
  #
134
- def self.boolean(val, msg = nil)
135
- msg ||= 'Value must be boolean true or false'
136
- return true if val.to_s =~ /^(true|yes)$/i
137
- return false if val.to_s =~ /^(false|no)$/i
149
+ def self.boolean(val, msg = 'Value must be true or false, or equivalent string or symbol')
150
+ return val if TRUE_FALSE.include? val
151
+ return true if val.to_s =~ /^(t(rue)?|y(es)?)$/i
152
+ return false if val.to_s =~ /^(f(alse)?|no?)$/i
138
153
 
139
154
  raise Jamf::InvalidDataError, msg
140
155
  end
141
156
 
157
+ # Confirm that a value provided is an integer or a string version
158
+ # of an integer, and return the string version
159
+ #
160
+ # The JPAPI specs say that all IDs are integers in strings
161
+ # tho, the endpoints are still implementing that in different versions.
162
+ #
163
+ # @param val[Object] the value to validate
164
+ #
165
+ # @param msg[String] A custom error message when the value is invalid
166
+ #
167
+ # @return [String] the valid integer-in-a-string
168
+ #
169
+ def self.j_id(val, msg = 'Value must be an Integer or an Integer in a String, e.g. "42"')
170
+ case val
171
+ when Integer
172
+ return val.to_s
173
+ when String
174
+ return val if val.j_integer?
175
+ end
176
+ raise Jamf::InvalidDataError, msg
177
+ end
178
+
142
179
  # Confirm that a value is an Integer or a String representation of an
143
180
  # Integer. Return the integer, or raise an error
144
181
  #
@@ -148,10 +185,10 @@ module Jamf
148
185
  #
149
186
  # @return [Integer] the valid integer
150
187
  #
151
- def self.integer(val, msg = nil)
152
- msg ||= 'Value must be an Integer'
188
+ def self.integer(val, msg = 'Value must be an Integer')
153
189
  val = val.to_i if val.is_a?(String) && val.j_integer?
154
190
  raise Jamf::InvalidDataError, msg unless val.is_a? Integer
191
+
155
192
  val
156
193
  end
157
194
 
@@ -164,10 +201,10 @@ module Jamf
164
201
  #
165
202
  # @return [Float] the valid float
166
203
  #
167
- def self.float(val, msg = nil)
168
- msg ||= 'Value must be a Floating Point number'
204
+ def self.float(val, msg = 'Value must be a Floating Point number')
169
205
  val = val.to_f if val.is_a?(String) && val.j_float?
170
- raise Jamf::InvalidDataError, msg unless val.is_a? Flot
206
+ raise Jamf::InvalidDataError, msg unless val.is_a? Float
207
+
171
208
  val
172
209
  end
173
210
 
@@ -180,11 +217,12 @@ module Jamf
180
217
  #
181
218
  # @return [String] the valid String
182
219
  #
183
- def self.string(val, msg = nil)
184
- msg ||= 'Value must be a String'
220
+ def self.string(val, msg = 'Value must be a String')
185
221
  return Jamf::BLANK if val.nil?
222
+
186
223
  val = val.to_s if val.is_a? Symbol
187
224
  raise Jamf::InvalidDataError, msg unless val.is_a? String
225
+
188
226
  val
189
227
  end
190
228
 
@@ -197,10 +235,10 @@ module Jamf
197
235
  #
198
236
  # @return [String] the valid non-empty string
199
237
  #
200
- def self.non_empty_string(val, msg = nil)
201
- msg ||= 'value must be a non-empty String'
238
+ def self.non_empty_string(val, msg = 'value must be a non-empty String')
202
239
  val = val.to_s if val.is_a? Symbol
203
240
  raise Jamf::InvalidDataError, msg unless val.is_a?(String) && !val.empty?
241
+
204
242
  val
205
243
  end
206
244
 
@@ -214,11 +252,12 @@ module Jamf
214
252
  #
215
253
  # @return [String] the validated string
216
254
  #
217
- def self.script_contents(val, msg = nil)
218
- msg ||= "value must be a String starting with '#!'"
255
+ def self.script_contents(val, msg = "value must be a String starting with '#!'")
219
256
  raise Jamf::InvalidDataError, msg unless val.is_a?(String) && val.start_with?(SCRIPT_SHEBANG)
257
+
220
258
  val
221
259
  end
260
+
222
261
  end # module validate
223
262
 
224
263
  end # module JSS
@@ -27,6 +27,6 @@
27
27
  module Jamf
28
28
 
29
29
  ### The version of the Jamf module
30
- VERSION = '0.0.1'.freeze
30
+ VERSION = '0.0.5'.freeze
31
31
 
32
32
  end # module
data/lib/jss.rb CHANGED
@@ -58,7 +58,8 @@ module JSS
58
58
 
59
59
  ###################
60
60
  ### Gems
61
- require 'rest-client'
61
+ require 'faraday'
62
+ require 'faraday_middleware'
62
63
  require 'plist'
63
64
  require 'immutable-struct'
64
65
  require 'recursive-open-struct'
@@ -197,7 +198,9 @@ module JSS
197
198
  class DistributionPoint < JSS::APIObject; end
198
199
  class EBook < JSS::APIObject; end
199
200
  class IBeacon < JSS::APIObject; end
201
+ class DockItem < JSS::APIObject; end
200
202
  class LDAPServer < JSS::APIObject; end
203
+ class DirectoryBinding < JSS::APIObject; end
201
204
  class MacApplication < JSS::APIObject; end
202
205
  class MobileDevice < JSS::APIObject; end
203
206
  class MobileDeviceApplication < JSS::APIObject; end
@@ -25,18 +25,6 @@
25
25
  ###
26
26
  module JSS
27
27
 
28
- # Constants
29
- #####################################
30
-
31
- # Module Variables
32
- #####################################
33
-
34
- # Module Methods
35
- #####################################
36
-
37
- # Classes
38
- #####################################
39
-
40
28
  # Instances of this class represent a REST connection to a JSS API.
41
29
  #
42
30
  # For most cases, a single connection to a single JSS is all you need, and
@@ -271,7 +259,7 @@ module JSS
271
259
  # {#get_rsrc}, {#put_rsrc}, {#post_rsrc}, & {#delete_rsrc}
272
260
  # documented below.
273
261
  #
274
- # For even lower-level work, you can access the underlying RestClient::Resource
262
+ # For even lower-level work, you can access the underlying Faraday::Connection
275
263
  # inside the APIConnection via the connection's {#cnx} attribute.
276
264
  #
277
265
  # APIConnection instances also have a {#server} attribute which contains an
@@ -330,6 +318,12 @@ module JSS
330
318
  # values for the format param of get_rsrc
331
319
  GET_FORMATS = %i[json xml].freeze
332
320
 
321
+ HTTP_ACCEPT_HEADER = 'Accept'.freeze
322
+ HTTP_CONTENT_TYPE_HEADER = 'Content-Type'.freeze
323
+
324
+ MIME_JSON = 'application/json'.freeze
325
+ MIME_XML = 'application/xml'.freeze
326
+
333
327
  # Attributes
334
328
  #####################################
335
329
 
@@ -337,7 +331,7 @@ module JSS
337
331
  attr_reader :user
338
332
  alias jss_user user
339
333
 
340
- # @return [RestClient::Resource] the underlying connection resource
334
+ # @return [Faraday::Connection] the underlying connection resource
341
335
  attr_reader :cnx
342
336
 
343
337
  # @return [Boolean] are we connected right now?
@@ -359,7 +353,7 @@ module JSS
359
353
  # @return [String] the protocol being used: http or https
360
354
  attr_reader :protocol
361
355
 
362
- # @return [RestClient::Response] The response from the most recent API call
356
+ # @return [Faraday::Response] The response from the most recent API call
363
357
  attr_reader :last_http_response
364
358
 
365
359
  # @return [String] The base URL to to the current REST API
@@ -453,8 +447,6 @@ module JSS
453
447
  # @option args :use_ssl[Boolean] should the connection be made over SSL? Defaults to true.
454
448
  #
455
449
  # @option args :verify_cert[Boolean] should HTTPS SSL certificates be verified. Defaults to true.
456
- # If your connection raises RestClient::SSLCertificateNotVerified, and you don't care about the
457
- # validity of the SSL cert. just set this explicitly to false.
458
450
  #
459
451
  # @option args :user[String] a JSS user who has API privs, required if not defined in JSS::CONFIG
460
452
  #
@@ -494,7 +486,7 @@ module JSS
494
486
  args[:password] = acquire_password args
495
487
 
496
488
  # heres our connection
497
- @cnx = RestClient::Resource.new(@rest_url.to_s, args)
489
+ @cnx = create_connection args[:password]
498
490
 
499
491
  verify_server_version
500
492
 
@@ -543,8 +535,7 @@ module JSS
543
535
  @connected = false
544
536
  end # disconnect
545
537
 
546
- # Get an arbitrary JSS resource
547
- #
538
+ # Get a JSS resource
548
539
  # The first argument is the resource to get (the part of the API url
549
540
  # after the 'JSSResource/' ) The resource must be properly URL escaped
550
541
  # beforehand. Note: URL.encode is deprecated, use CGI.escape
@@ -572,14 +563,19 @@ module JSS
572
563
  validate_connected
573
564
  raise JSS::InvalidDataError, 'format must be :json or :xml' unless GET_FORMATS.include? format
574
565
 
575
- begin
576
- @last_http_response = @cnx[rsrc].get(accept: format)
577
- return JSON.parse(@last_http_response.body, symbolize_names: true) if format == :json && !raw_json
566
+ @last_http_response =
567
+ @cnx.get(rsrc) do |req|
568
+ req.headers[HTTP_ACCEPT_HEADER] = format == :json ? MIME_JSON : MIME_XML
569
+ end
578
570
 
579
- @last_http_response.body
580
- rescue RestClient::ExceptionWithResponse => e
581
- handle_http_error e
571
+ unless @last_http_response.success?
572
+ handle_http_error
573
+ return
582
574
  end
575
+
576
+ return JSON.parse(@last_http_response.body, symbolize_names: true) if format == :json && !raw_json
577
+
578
+ @last_http_response.body
583
579
  end
584
580
 
585
581
  # Update an existing JSS resource
@@ -597,10 +593,18 @@ module JSS
597
593
  xml.gsub!(/\r/, '&#13;')
598
594
 
599
595
  # send the data
600
- @last_http_response = @cnx[rsrc].put(xml, content_type: 'text/xml')
596
+ @last_http_response =
597
+ @cnx.put(rsrc) do |req|
598
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
599
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
600
+ req.body = xml
601
+ end
602
+ unless @last_http_response.success?
603
+ handle_http_error
604
+ return
605
+ end
606
+
601
607
  @last_http_response.body
602
- rescue RestClient::ExceptionWithResponse => e
603
- handle_http_error e
604
608
  end
605
609
 
606
610
  # Create a new JSS resource
@@ -611,17 +615,24 @@ module JSS
611
615
  #
612
616
  # @return [String] the xml response from the server.
613
617
  #
614
- def post_rsrc(rsrc, xml = '')
618
+ def post_rsrc(rsrc, xml)
615
619
  validate_connected
616
620
 
617
621
  # convert CRs & to &#13;
618
- xml.gsub!(/\r/, '&#13;') if xml
622
+ xml&.gsub!(/\r/, '&#13;')
619
623
 
620
624
  # send the data
621
- @last_http_response = @cnx[rsrc].post(xml, content_type: 'text/xml', accept: :json)
625
+ @last_http_response =
626
+ @cnx.post(rsrc) do |req|
627
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
628
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
629
+ req.body = xml
630
+ end
631
+ unless @last_http_response.success?
632
+ handle_http_error
633
+ return
634
+ end
622
635
  @last_http_response.body
623
- rescue RestClient::ExceptionWithResponse => e
624
- handle_http_error e
625
636
  end # post_rsrc
626
637
 
627
638
  # Delete a resource from the JSS
@@ -630,18 +641,23 @@ module JSS
630
641
  #
631
642
  # @return [String] the xml response from the server.
632
643
  #
633
- def delete_rsrc(rsrc, xml = nil)
644
+ def delete_rsrc(rsrc)
634
645
  validate_connected
635
646
  raise MissingDataError, 'Missing :rsrc' if rsrc.nil?
636
647
 
637
- # payload?
638
- return delete_with_payload rsrc, xml if xml
639
-
640
648
  # delete the resource
641
- @last_http_response = @cnx[rsrc].delete
649
+ @last_http_response =
650
+ @cnx.delete(rsrc) do |req|
651
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
652
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
653
+ end
654
+
655
+ unless @last_http_response.success?
656
+ handle_http_error
657
+ return
658
+ end
659
+
642
660
  @last_http_response.body
643
- rescue RestClient::ExceptionWithResponse => e
644
- handle_http_error e
645
661
  end # delete_rsrc
646
662
 
647
663
  # Test that a given hostname & port is a JSS API server
@@ -657,25 +673,8 @@ module JSS
657
673
  # ssl_options like :OP_NO_SSLv2 and :OP_NO_SSLv3 will take time to figure out..
658
674
  return true if `/usr/bin/curl -s 'https://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
659
675
  return true if `/usr/bin/curl -s 'http://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
660
- false
661
676
 
662
- # # try ssl first
663
- # # NOTE: doesn't work if we can't disallow SSLv3 or force TLSv1
664
- # # See cheat above.
665
- # begin
666
- # return true if open("https://#{server}:#{port}/#{TEST_PATH}", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE).read.include? TEST_CONTENT
667
- #
668
- # rescue
669
- # # then regular http
670
- # begin
671
- # return true if open("http://#{server}:#{port}/#{TEST_PATH}").read.include? TEST_CONTENT
672
- # rescue
673
- # # any errors = no API
674
- # return false
675
- # end # begin
676
- # end # begin
677
- # # if we're here, no API
678
- # false
677
+ false
679
678
  end
680
679
 
681
680
  # The server to which we are connected, or will
@@ -686,294 +685,13 @@ module JSS
686
685
  #
687
686
  def hostname
688
687
  return @server_host if @server_host
688
+
689
689
  srvr = JSS::CONFIG.api_server_name
690
690
  srvr ||= JSS::Client.jss_server
691
691
  srvr
692
692
  end
693
693
  alias host hostname
694
694
 
695
- #################
696
-
697
- # Call one of the 'all*' methods on a JSS::APIObject subclass
698
- # using this APIConnection.
699
- #
700
- #
701
- # @deprecated please use the .all class method of the desired class
702
- #
703
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
704
- # see {JSS.api_object_class}
705
- #
706
- # @param refresh[Boolean] Should the data be re-read from the API?
707
- #
708
- # @param only[String,Symbol] Limit the output to subset or data. All
709
- # APIObject subclasses can take :ids or :names, which calls the .all_ids
710
- # and .all_names methods. Some subclasses can take other options, e.g.
711
- # MobileDevice can take :udids
712
- #
713
- # @return [Array] The list of items for the class
714
- #
715
- def all(class_name, refresh = false, only: nil)
716
- the_class = JSS.api_object_class(class_name)
717
- list_method = only ? :"all_#{only}" : :all
718
-
719
- raise ArgumentError, "Unknown identifier: #{only} for #{the_class}" unless
720
- the_class.respond_to? list_method
721
-
722
- the_class.send list_method, refresh, api: self
723
- end
724
-
725
- # Call the 'map_all_ids_to' method on a JSS::APIObject subclass
726
- # using this APIConnection.
727
- #
728
- # @deprecated please use the .map_all_ids_to class method of the desired class
729
- #
730
- #
731
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
732
- # see {JSS.api_object_class}
733
- #
734
- # @param refresh[Boolean] Should the data be re-read from the API?
735
- #
736
- # @param to[String,Symbol] the value to which the ids should be mapped
737
- #
738
- # @return [Hash] The ids for the class keyed to the requested identifier
739
- #
740
- def map_all_ids(class_name, refresh = false, to: nil)
741
- raise "'to:' value must be provided for mapping ids." unless to
742
- the_class = JSS.api_object_class(class_name)
743
- the_class.map_all_ids_to to, refresh, api: self
744
- end
745
-
746
- # Call the 'valid_id' method on a JSS::APIObject subclass
747
- # using this APIConnection. See {JSS::APIObject.valid_id}
748
- #
749
- # @deprecated please use the .valid_id class method of the desired class
750
- #
751
- #
752
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass,
753
- # see {JSS.api_object_class}
754
- #
755
- # @param identifier[String,Symbol] the value to which the ids should be mapped
756
- #
757
- # @param refresh[Boolean] Should the data be re-read from the API?
758
- #
759
- # @return [Integer, nil] the id of the matching object of the class,
760
- # or nil if there isn't one
761
- #
762
- def valid_id(class_name, identifier, refresh = true)
763
- the_class = JSS.api_object_class(class_name)
764
- the_class.valid_id identifier, refresh, api: self
765
- end
766
-
767
- # Call the 'exist?' method on a JSS::APIObject subclass
768
- # using this APIConnection. See {JSS::APIObject.exist?}
769
- #
770
- # @deprecated please use the .exist class method of the desired class
771
- #
772
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
773
- # see {JSS.api_object_class}
774
- #
775
- # @param identifier[String,Symbol] the value to which the ids should be mapped
776
- #
777
- # @param refresh[Boolean] Should the data be re-read from the API?
778
- #
779
- # @return [Boolean] Is there an object of this class in the JSS matching
780
- # this indentifier?
781
- #
782
- def exist?(class_name, identifier, refresh = false)
783
- !valid_id(class_name, identifier, refresh).nil?
784
- end
785
-
786
- # Call {Matchable.match} for the given class.
787
- #
788
- # See {Matchable.match}
789
- #
790
- # @deprecated Please use the .match class method of the desired class
791
- #
792
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
793
- # see {JSS.api_object_class}
794
- #
795
- # @return (see Matchable.match)
796
- #
797
- def match(class_name, term)
798
- the_class = JSS.api_object_class(class_name)
799
- raise JSS::UnsupportedError, "Class #{the_class} is not matchable" unless the_class.respond_to? :match
800
- the_class.match term, api: self
801
- end
802
-
803
- # Retrieve an object of a given class from the API
804
- # See {APIObject.fetch}
805
- #
806
- # @deprecated Please use the .fetch class method of the desired class
807
- #
808
- #
809
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
810
- # see {JSS.api_object_class}
811
- #
812
- # @return [APIObject] The ruby-instance of the object.
813
- #
814
- def fetch(class_name, arg)
815
- the_class = JSS.api_object_class(class_name)
816
- the_class.fetch arg, api: self
817
- end
818
-
819
- # Make a ruby instance of a not-yet-existing APIObject
820
- # of the given class
821
- # See {APIObject.make}
822
- #
823
- # @deprecated Please use the .make class method of the desired class
824
- #
825
- # @param class_name[String,Symbol] The name of a JSS::APIObject subclass
826
- # see {JSS.api_object_class}
827
- #
828
- # @return [APIObject] The un-created ruby-instance of the object.
829
- #
830
- def make(class_name, **args)
831
- the_class = JSS.api_object_class(class_name)
832
- args[:api] = self
833
- the_class.make args
834
- end
835
-
836
- # Call {JSS::Computer.checkin_settings} q.v., passing this API
837
- # connection
838
- # @deprecated Please use JSS::Computer.checkin_settings
839
- #
840
- def computer_checkin_settings
841
- JSS::Computer.checkin_settings api: self
842
- end
843
-
844
- # Call {JSS::Computer.inventory_collection_settings} q.v., passing this API
845
- # connection
846
- # @deprecated Please use JSS::Computer.inventory_collection_settings
847
- #
848
- def computer_inventory_collection_settings
849
- JSS::Computer.inventory_collection_settings api: self
850
- end
851
-
852
- # Call {JSS::Computer.application_usage} q.v., passing this API
853
- # connection
854
- # @deprecated Please use JSS::Computer.application_usage
855
- #
856
- def computer_application_usage(ident, start_date, end_date = nil)
857
- JSS::Computer.application_usage ident, start_date, end_date, api: self
858
- end
859
-
860
- # Call {JSS::Computer.management_data} q.v., passing this API
861
- # connection
862
- #
863
- # @deprecated Please use JSS::Computer.management_data
864
- #
865
- def computer_management_data(ident, subset: nil, only: nil)
866
- JSS::Computer.management_data ident, subset: subset, only: only, api: self
867
- end
868
-
869
- # Call {JSS::Computer.history} q.v., passing this API
870
- # connection
871
- #
872
- # @deprecated Please use JSS::Computer.management_history or its
873
- # convenience methods. @see JSS::ManagementHistory
874
- #
875
- def computer_history(ident, subset: nil)
876
- JSS::Computer.history ident, subset, api: self
877
- end
878
-
879
- # Call {JSS::Computer.send_mdm_command} q.v., passing this API
880
- # connection
881
- #
882
- # @deprecated Please use JSS::Computer.send_mdm_command or its
883
- # convenience methods. @see JSS::MDM
884
- #
885
- def send_computer_mdm_command(targets, command, passcode = nil)
886
- opts = passcode ? { passcode: passcode } : {}
887
- JSS::Computer.send_mdm_command targets, command, opts: opts, api: self
888
- end
889
-
890
- # Get the DistributionPoint instance for the master
891
- # distribution point in the JSS. If there's only one
892
- # in the JSS, return it even if not marked as master.
893
- #
894
- # @param refresh[Boolean] re-read from the API?
895
- #
896
- # @return [JSS::DistributionPoint]
897
- #
898
- def master_distribution_point(refresh = false)
899
- @master_distribution_point = nil if refresh
900
- return @master_distribution_point if @master_distribution_point
901
-
902
- JSS::DistributionPoint.all_ids.each do |dp_id|
903
- dp = JSS::DistributionPoint.fetch id: dp_id, api: self
904
- if dp.master?
905
- @master_distribution_point = dp
906
- break
907
- end
908
- end
909
-
910
- return @master_distribution_point if @master_distribution_point
911
-
912
- # If we're here, the Cloud DP might be master, but there's no
913
- # access to it in the API :/
914
- raise JSS::NoSuchItemError, 'No Master Distribtion Point defined. It could be the Cloud Dist Point, which is not available in the classic API'
915
- end
916
-
917
- # Get the DistributionPoint instance for the machine running
918
- # this code, based on its IP address. If none is defined for this IP address,
919
- # use the result of master_distribution_point
920
- #
921
- # @param refresh[Boolean] should the distribution point be re-queried?
922
- #
923
- # @return [JSS::DistributionPoint]
924
- #
925
- def my_distribution_point(refresh = false)
926
- @my_distribution_point = nil if refresh
927
- return @my_distribution_point if @my_distribution_point
928
-
929
- my_net_seg_id = my_network_segments[0]
930
-
931
- if my_net_seg_id
932
- my_net_seg = JSS::NetworkSegment.fetch(id: my_net_seg_id, api: self)
933
- my_dp_name = my_net_seg.distribution_point
934
- @my_distribution_point = JSS::DistributionPoint.fetch(name: my_dp_name) if my_dp_name
935
- end # if my_net_seg_id
936
-
937
- @my_distribution_point ||= master_distribution_point refresh
938
- @my_distribution_point
939
- end
940
-
941
- # @deprecated
942
- #
943
- # @see {JSS::NetworkSegment.network_ranges}
944
- #
945
- def network_ranges(refresh = false)
946
- JSS::NetworkSegment.network_ranges refresh, api: self
947
- end # def network_segments
948
-
949
- # @deprecated
950
- #
951
- # @see {JSS::NetworkSegment.network_segments_for_ip}
952
- #
953
- def network_segments_for_ip(ip, refresh = false)
954
- JSS::NetworkSegment.network_segments_for_ip ip, refresh, api: self
955
- end
956
-
957
- # @deprecated
958
- #
959
- # @see {JSS::NetworkSegment.my_network_segments}
960
- #
961
- def my_network_segments
962
- network_segments_for_ip JSS::Client.my_ip_address
963
- end
964
-
965
- # Send an MDM command to one or more mobile devices managed by
966
- # this JSS
967
- #
968
- # see {JSS::MobileDevice.send_mdm_command}
969
- #
970
- # @deprecated Please use JSS::MobileDevice.send_mdm_command or its
971
- # convenience methods. @see JSS::MDM
972
- #
973
- def send_mobiledevice_mdm_command(targets, command, data = {})
974
- JSS::MobileDevice.send_mdm_command(targets, command, opts: data, api: self)
975
- end
976
-
977
695
  # Empty all cached lists from this connection
978
696
  # then run garbage collection to clear any available memory
979
697
  #
@@ -1079,6 +797,7 @@ module JSS
1079
797
  #
1080
798
  def apply_defaults_from_client(args)
1081
799
  return unless JSS::Client.installed?
800
+
1082
801
  # these settings can come from the jamf binary config, if this machine is a JSS client.
1083
802
  args[:server] ||= JSS::Client.jss_server
1084
803
  args[:port] ||= JSS::Client.jss_port.to_i
@@ -1132,11 +851,13 @@ module JSS
1132
851
  # keep this basic level of info available for basic authentication
1133
852
  # and JSS version checking.
1134
853
  begin
1135
- @server = JSS::Server.new get_rsrc('jssuser')[:user], self
1136
- rescue RestClient::Unauthorized
854
+ data = get_rsrc('jssuser')
855
+ rescue JSS::AuthorizationError
1137
856
  raise JSS::AuthenticationError, "Incorrect JSS username or password for '#{@user}@#{@server_host}:#{@port}'."
1138
857
  end
1139
858
 
859
+ @server = JSS::Server.new data[:user], self
860
+
1140
861
  min_vers = JSS.parse_jss_version(JSS::MINIMUM_SERVER_VERSION)[:version]
1141
862
  return if @server.version >= min_vers # we're good...
1142
863
 
@@ -1205,77 +926,68 @@ module JSS
1205
926
  if SSL_PORTS.include? args[:port]
1206
927
  args[:use_ssl] = true unless args[:use_ssl] == false
1207
928
  end
929
+ return unless args[:use_ssl]
930
+
1208
931
  # if verify_cert is anything but false, we will verify
1209
- args[:verify_ssl] = args[:verify_cert] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
932
+ args[:verify_ssl] = args[:verify_cert] != false
933
+
934
+ # ssl version if not specified
935
+ args[:ssl_version] ||= DFT_SSL_VERSION
936
+
937
+ @ssl_options = {
938
+ verify: args[:verify_ssl],
939
+ version: args[:ssl_version]
940
+ }
1210
941
  end
1211
942
 
1212
- # Parses the HTTP body of a RestClient::ExceptionWithResponse
1213
- # (the parent of all HTTP error responses) and its subclasses
1214
- # and re-raises a JSS::APIError with a more
1215
- # useful error message.
1216
- #
1217
- # @param exception[RestClient::ExceptionWithResponse] the exception to parse
943
+ # Parses the @last_http_response
944
+ # and raises a JSS::APIError with a useful error message.
1218
945
  #
1219
946
  # @return [void]
1220
947
  #
1221
- def handle_http_error(exception)
1222
- @last_http_response = exception.response
1223
- case exception
1224
- when RestClient::ResourceNotFound
1225
- # other methods catch this and report more details
1226
- raise exception
1227
- when RestClient::Conflict
948
+ def handle_http_error
949
+ return if @last_http_response.success?
950
+
951
+ case @last_http_response.status
952
+ when 404
953
+ err = JSS::NoSuchItemError
954
+ msg = 'Not Found'
955
+ when 409
1228
956
  err = JSS::ConflictError
1229
- msg_matcher = /<p>Error:(.*?)(<|$)/m
1230
- when RestClient::BadRequest
957
+ @last_http_response.body =~ /<p>(The server has not .*?)(<|$)/m
958
+ msg = Regexp.last_match(1)
959
+ when 400
1231
960
  err = JSS::BadRequestError
1232
- msg_matcher = %r{>Bad Request</p>\n<p>(.*?)</p>\n<p>You can get technical detail}m
1233
- when RestClient::Unauthorized
1234
- raise
961
+ @last_http_response.body =~ %r{>Bad Request</p>\n<p>(.*?)</p>\n<p>You can get technical detail}m
962
+ msg = Regexp.last_match(1)
963
+ when 401
964
+ err = JSS::AuthorizationError
965
+ msg = 'You are not authorized to do that.'
966
+ when (500..599)
967
+ err = JSS::APIRequestError
968
+ msg = 'There was an internal server error'
1235
969
  else
1236
970
  err = JSS::APIRequestError
1237
- msg_matcher = %r{<body.*?>(.*?)</body>}m
971
+ msg = "There was a error processing your request, status: #{@last_http_response.status}"
1238
972
  end
1239
- exception.http_body =~ msg_matcher
1240
- msg = Regexp.last_match(1)
1241
- msg ||= exception.http_body
1242
973
  raise err, msg
1243
974
  end
1244
975
 
1245
- # RestClient::Resource#delete doesn't take an HTTP payload,
1246
- # but some JSS API resources require it (notably, logflush).
1247
- #
1248
- # This method uses RestClient::Request#execute
1249
- # to do the same thing that RestClient::Resource#delete does, but
1250
- # adding the payload.
1251
- #
1252
- # @param rsrc[String] the sub-resource we're DELETEing
1253
- #
1254
- # @param payload[String] The XML to be passed with the DELETE
1255
- #
1256
- # @param additional_headers[Type] See RestClient::Request#execute
1257
- #
1258
- # @param &block[Type] See RestClient::Request#execute
1259
- #
1260
- # @return [String] the XML response from the server.
1261
- #
1262
- def delete_with_payload(rsrc, payload, additional_headers = {}, &block)
1263
- headers = (@cnx.options[:headers] || {}).merge(additional_headers)
1264
- @last_http_response = RestClient::Request.execute(
1265
- @cnx.options.merge(
1266
- method: :delete,
1267
- url: @cnx[rsrc].url,
1268
- payload: payload,
1269
- headers: headers
1270
- ),
1271
- &(block || @block)
1272
- )
1273
- rescue RestClient::ExceptionWithResponse => e
1274
- handle_http_error e
1275
- end # delete_with_payload
976
+ # create the faraday connection object
977
+ def create_connection(pw)
978
+ Faraday.new(@rest_url, ssl: @ssl_options) do |cnx|
979
+ cnx.basic_auth @user, pw
980
+ cnx.options[:timeout] = @timeout
981
+ cnx.options[:open_timeout] = @open_timeout
982
+ cnx.adapter Faraday::Adapter::NetHttp
983
+ end
984
+ end
1276
985
 
1277
986
  end # class APIConnection
1278
987
 
988
+ # JSS MODULE METHODS
989
+ ######################
990
+
1279
991
  # Create a new APIConnection object and use it for all
1280
992
  # future API calls. If connection options are provided,
1281
993
  # they are passed to the connect method immediately, otherwise
@@ -1303,6 +1015,7 @@ module JSS
1303
1015
  #
1304
1016
  def self.use_api_connection(connection)
1305
1017
  raise 'API connections must be instances of JSS::APIConnection' unless connection.is_a? JSS::APIConnection
1018
+
1306
1019
  @api = connection
1307
1020
  end
1308
1021