ruby-jss 1.2.10 → 1.5.2

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +208 -1
  3. data/lib/jamf.rb +18 -16
  4. data/lib/jamf/api/base_classes/collection_resource.rb +613 -0
  5. data/lib/jamf/api/{abstract_classes → base_classes}/json_object.rb +110 -102
  6. data/lib/jamf/api/{abstract_classes → base_classes}/prestage.rb +56 -31
  7. data/lib/jamf/api/{abstract_classes → base_classes}/resource.rb +10 -6
  8. data/lib/jamf/api/{abstract_classes → base_classes}/singleton_resource.rb +4 -3
  9. data/lib/jamf/api/connection.rb +20 -12
  10. data/lib/jamf/api/connection/api_error.rb +8 -8
  11. data/lib/jamf/api/connection/token.rb +36 -15
  12. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  13. data/lib/jamf/api/json_objects/{location.rb → device_enrollment_device_sync_state.rb} +27 -41
  14. data/lib/jamf/api/json_objects/device_enrollment_sync_status.rb +1 -1
  15. data/lib/jamf/api/json_objects/{attachment.rb → locale.rb} +14 -23
  16. data/lib/jamf/api/json_objects/md_prestage_name.rb +1 -1
  17. data/lib/jamf/api/json_objects/md_prestage_names.rb +2 -2
  18. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  19. data/lib/jamf/api/json_objects/prestage_assignment.rb +2 -2
  20. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  21. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  22. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  23. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  24. data/lib/jamf/api/mixins/{abstract.rb → base_class.rb} +34 -16
  25. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  26. data/lib/jamf/api/mixins/change_log.rb +201 -51
  27. data/lib/jamf/api/{resources/collection_resources/extension_attribute.rb → mixins/filterable.rb} +20 -14
  28. data/lib/jamf/api/mixins/pageable.rb +208 -0
  29. data/lib/jamf/api/{json_objects/installed_application.rb → mixins/sortable.rb} +33 -33
  30. data/lib/jamf/api/resources/collection_resources/building.rb +16 -9
  31. data/lib/jamf/api/resources/collection_resources/category.rb +5 -4
  32. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +12 -5
  33. data/lib/jamf/api/resources/collection_resources/department.rb +1 -3
  34. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  35. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  36. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +25 -23
  37. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  38. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  39. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  40. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  41. data/lib/jamf/configuration.rb +7 -9
  42. data/lib/jamf/ruby_extensions.rb +1 -0
  43. data/lib/jamf/ruby_extensions/array.rb +1 -1
  44. data/lib/jamf/ruby_extensions/array/utils.rb +3 -3
  45. data/lib/jamf/{api/resources/collection_resources/computer.rb → ruby_extensions/dig.rb} +22 -19
  46. data/lib/jamf/validate.rb +63 -24
  47. data/lib/jamf/version.rb +1 -1
  48. data/lib/jss.rb +4 -1
  49. data/lib/jss/api_connection.rb +111 -433
  50. data/lib/jss/api_object.rb +16 -13
  51. data/lib/jss/api_object/advanced_search.rb +27 -26
  52. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  53. data/lib/jss/api_object/categorizable.rb +1 -1
  54. data/lib/jss/api_object/computer.rb +13 -0
  55. data/lib/jss/api_object/configuration_profile.rb +60 -4
  56. data/lib/jss/api_object/directory_binding.rb +273 -0
  57. data/lib/jss/api_object/directory_binding_type.rb +96 -0
  58. data/lib/jss/api_object/directory_binding_type/active_directory.rb +539 -0
  59. data/lib/jss/api_object/directory_binding_type/admitmac.rb +594 -0
  60. data/lib/jss/api_object/directory_binding_type/centrify.rb +226 -0
  61. data/lib/jss/api_object/directory_binding_type/open_directory.rb +178 -0
  62. data/lib/jss/api_object/directory_binding_type/powerbroker_identity_services.rb +73 -0
  63. data/lib/jss/api_object/disk_encryption_configurations.rb +114 -0
  64. data/lib/jss/api_object/distribution_point.rb +97 -37
  65. data/lib/jss/api_object/dock_item.rb +143 -0
  66. data/lib/jss/api_object/ebook.rb +1 -2
  67. data/lib/jss/api_object/extendable.rb +1 -1
  68. data/lib/jss/api_object/extension_attribute.rb +4 -3
  69. data/lib/jss/api_object/group.rb +33 -2
  70. data/lib/jss/api_object/mac_application.rb +107 -8
  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 +596 -32
  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 +371 -55
  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 +143 -52
  88. data/lib/jss/validate.rb +53 -10
  89. data/lib/jss/version.rb +1 -1
  90. metadata +56 -61
  91. data/lib/jamf/api/abstract_classes/advanced_search.rb +0 -86
  92. data/lib/jamf/api/abstract_classes/collection_resource.rb +0 -433
  93. data/lib/jamf/api/abstract_classes/generic_reference.rb +0 -145
  94. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +0 -126
  95. data/lib/jamf/api/json_objects/account_prefs.rb +0 -79
  96. data/lib/jamf/api/json_objects/android_details.rb +0 -139
  97. data/lib/jamf/api/json_objects/appletv_details.rb +0 -110
  98. data/lib/jamf/api/json_objects/cellular_network.rb +0 -151
  99. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +0 -67
  100. data/lib/jamf/api/json_objects/criterion.rb +0 -152
  101. data/lib/jamf/api/json_objects/extension_attribute_value.rb +0 -128
  102. data/lib/jamf/api/json_objects/installed_certificate.rb +0 -53
  103. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +0 -67
  104. data/lib/jamf/api/json_objects/installed_ebook.rb +0 -58
  105. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +0 -59
  106. data/lib/jamf/api/json_objects/ios_details.rb +0 -244
  107. data/lib/jamf/api/json_objects/mobile_device_details.rb +0 -219
  108. data/lib/jamf/api/json_objects/mobile_device_security.rb +0 -101
  109. data/lib/jamf/api/json_objects/purchasing_data.rb +0 -125
  110. data/lib/jamf/api/mixins/locatable.rb +0 -124
  111. data/lib/jamf/api/mixins/referable.rb +0 -92
  112. data/lib/jamf/api/resources/collection_resources/account.rb +0 -163
  113. data/lib/jamf/api/resources/collection_resources/advanced_mobile_device_search.rb +0 -52
  114. data/lib/jamf/api/resources/collection_resources/advanced_user_search.rb +0 -52
  115. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +0 -315
  116. data/lib/jamf/api/resources/collection_resources/site.rb +0 -77
  117. data/lib/jamf/api/resources/singleton_resources/authorization.rb +0 -88
  118. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +0 -139
  119. data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +0 -95
@@ -20,30 +20,33 @@
20
20
  # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
21
  # KIND, either express or implied. See the Apache License for the specific
22
22
  # language governing permissions and limitations under the Apache License.
23
- #
24
- #
25
-
26
- # The Module
27
- module Jamf
28
23
 
29
- # Classes
30
- #####################################
24
+ # Gratefully borrowed from https://github.com/Invoca/ruby_dig
31
25
 
32
- # A building defined in the JSS
33
- class Computer < Jamf::CollectionResource
26
+ # modulize monkey patches
27
+ module RubyDig
34
28
 
35
- # Mix-Ins
36
- #####################################
37
- include Jamf::Referable
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
38
39
 
39
- # Constants
40
- #####################################
40
+ end
41
41
 
42
- RSRC_PATH = 'inventory/obj/computer'.freeze
42
+ if RUBY_VERSION < '2.3'
43
43
 
44
- OBJECT_MODEL = {}.freeze
45
- parse_object_model
44
+ # arrays
45
+ class Array; include RubyDig; end
46
46
 
47
- end # class
47
+ # hashes
48
+ class Hash; include RubyDig; end
48
49
 
49
- end # module
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.6a1'.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
@@ -227,42 +215,6 @@ module JSS
227
215
  # # the variable 'prod2_victim_md' now contains a JSS::MobileDevice queried
228
216
  # # through the connection 'production_api2'.
229
217
  #
230
- # == Using the APIConnection itself to make API calls.
231
- #
232
- # Rather than passing an APIConnection into another method, you can call
233
- # similar methods on the connection itself. For example, these two calls
234
- # have the same result as the two examples above:
235
- #
236
- # prod2_computer_sns = production_api2.all :Computer, only: :serial_numbers
237
- # prod2_victim_md = production_api2.fetch :MobileDevice, id: 832
238
- #
239
- # Here are the API calls you can make directly from an APIConnection object.
240
- # They behave practically identically to the same methods in the APIObject
241
- # subclasses, since they just call those methods, passing themselves in as the
242
- # APIConnection to use.
243
- #
244
- # - {#all} The 'list' methods of the various APIObject classes. Use the 'only:'
245
- # parameter to specify one of the sub-list-methods, like #all_ids or
246
- # #all_laptops, e.g. `my_connection.all :computers, only: :id`
247
- # - {#map_all_ids} the equivalent of #map_all_ids_to in the APIObject classes
248
- # - {#valid_id} given a class and an identifier (like macaddress or udid)
249
- # return a valid id or nil
250
- # - {#exist?} given a class and an identifier (like macaddress or udid) does
251
- # the identifier exist for the class in the JSS
252
- # - {#match} list items in the JSS matching a query
253
- # (if the object is {Matchable})
254
- # - {#fetch} retrieve an object from the JSS
255
- # - {#make} instantiate an object to be created in the JSS
256
- # - {#computer_checkin_settings} same as {Computer.checkin_settings}
257
- # - {#computer_inventory_collection_settings} same as {Computer.inventory_collection_settings}
258
- # - {#computer_application_usage} same as {Computer.application_usage}
259
- # - {#computer_management_data} same as {Computer.management_data}
260
- # - {#master_distribution_point} same as {DistributionPoint.master_distribution_point}
261
- # - {#my_distribution_point} same as {DistributionPoint.my_distribution_point}
262
- # - {#network_ranges} same as {NetworkSegment.network_ranges}
263
- # - {#network_segments_for_ip} same as {NetworkSegment.segments_for_ip}
264
- # - {#my_network_segments} same as {NetworkSegment.my_network_segments}
265
- #
266
218
  # == Low-level use of APIConnection instances.
267
219
  #
268
220
  # For most cases, using APIConnection instances as mentioned above
@@ -271,7 +223,7 @@ module JSS
271
223
  # {#get_rsrc}, {#put_rsrc}, {#post_rsrc}, & {#delete_rsrc}
272
224
  # documented below.
273
225
  #
274
- # For even lower-level work, you can access the underlying RestClient::Resource
226
+ # For even lower-level work, you can access the underlying Faraday::Connection
275
227
  # inside the APIConnection via the connection's {#cnx} attribute.
276
228
  #
277
229
  # APIConnection instances also have a {#server} attribute which contains an
@@ -330,6 +282,12 @@ module JSS
330
282
  # values for the format param of get_rsrc
331
283
  GET_FORMATS = %i[json xml].freeze
332
284
 
285
+ HTTP_ACCEPT_HEADER = 'Accept'.freeze
286
+ HTTP_CONTENT_TYPE_HEADER = 'Content-Type'.freeze
287
+
288
+ MIME_JSON = 'application/json'.freeze
289
+ MIME_XML = 'application/xml'.freeze
290
+
333
291
  # Attributes
334
292
  #####################################
335
293
 
@@ -337,7 +295,7 @@ module JSS
337
295
  attr_reader :user
338
296
  alias jss_user user
339
297
 
340
- # @return [RestClient::Resource] the underlying connection resource
298
+ # @return [Faraday::Connection] the underlying connection resource
341
299
  attr_reader :cnx
342
300
 
343
301
  # @return [Boolean] are we connected right now?
@@ -359,7 +317,7 @@ module JSS
359
317
  # @return [String] the protocol being used: http or https
360
318
  attr_reader :protocol
361
319
 
362
- # @return [RestClient::Response] The response from the most recent API call
320
+ # @return [Faraday::Response] The response from the most recent API call
363
321
  attr_reader :last_http_response
364
322
 
365
323
  # @return [String] The base URL to to the current REST API
@@ -428,6 +386,7 @@ module JSS
428
386
  @name = args.delete :name
429
387
  @name ||= :unknown
430
388
  @connected = false
389
+ @object_list_cache = {}
431
390
  connect args unless args.empty?
432
391
  end # init
433
392
 
@@ -453,8 +412,6 @@ module JSS
453
412
  # @option args :use_ssl[Boolean] should the connection be made over SSL? Defaults to true.
454
413
  #
455
414
  # @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
415
  #
459
416
  # @option args :user[String] a JSS user who has API privs, required if not defined in JSS::CONFIG
460
417
  #
@@ -494,7 +451,7 @@ module JSS
494
451
  args[:password] = acquire_password args
495
452
 
496
453
  # heres our connection
497
- @cnx = RestClient::Resource.new(@rest_url.to_s, args)
454
+ @cnx = create_connection args[:password]
498
455
 
499
456
  verify_server_version
500
457
 
@@ -543,8 +500,7 @@ module JSS
543
500
  @connected = false
544
501
  end # disconnect
545
502
 
546
- # Get an arbitrary JSS resource
547
- #
503
+ # Get a JSS resource
548
504
  # The first argument is the resource to get (the part of the API url
549
505
  # after the 'JSSResource/' ) The resource must be properly URL escaped
550
506
  # beforehand. Note: URL.encode is deprecated, use CGI.escape
@@ -572,14 +528,19 @@ module JSS
572
528
  validate_connected
573
529
  raise JSS::InvalidDataError, 'format must be :json or :xml' unless GET_FORMATS.include? format
574
530
 
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
531
+ @last_http_response =
532
+ @cnx.get(rsrc) do |req|
533
+ req.headers[HTTP_ACCEPT_HEADER] = format == :json ? MIME_JSON : MIME_XML
534
+ end
578
535
 
579
- @last_http_response.body
580
- rescue RestClient::ExceptionWithResponse => e
581
- handle_http_error e
536
+ unless @last_http_response.success?
537
+ handle_http_error
538
+ return
582
539
  end
540
+
541
+ return JSON.parse(@last_http_response.body, symbolize_names: true) if format == :json && !raw_json
542
+
543
+ @last_http_response.body
583
544
  end
584
545
 
585
546
  # Update an existing JSS resource
@@ -597,10 +558,18 @@ module JSS
597
558
  xml.gsub!(/\r/, '&#13;')
598
559
 
599
560
  # send the data
600
- @last_http_response = @cnx[rsrc].put(xml, content_type: 'text/xml')
561
+ @last_http_response =
562
+ @cnx.put(rsrc) do |req|
563
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
564
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
565
+ req.body = xml
566
+ end
567
+ unless @last_http_response.success?
568
+ handle_http_error
569
+ return
570
+ end
571
+
601
572
  @last_http_response.body
602
- rescue RestClient::ExceptionWithResponse => e
603
- handle_http_error e
604
573
  end
605
574
 
606
575
  # Create a new JSS resource
@@ -611,17 +580,24 @@ module JSS
611
580
  #
612
581
  # @return [String] the xml response from the server.
613
582
  #
614
- def post_rsrc(rsrc, xml = '')
583
+ def post_rsrc(rsrc, xml)
615
584
  validate_connected
616
585
 
617
586
  # convert CRs & to &#13;
618
- xml.gsub!(/\r/, '&#13;') if xml
587
+ xml&.gsub!(/\r/, '&#13;')
619
588
 
620
589
  # send the data
621
- @last_http_response = @cnx[rsrc].post(xml, content_type: 'text/xml', accept: :json)
590
+ @last_http_response =
591
+ @cnx.post(rsrc) do |req|
592
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
593
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
594
+ req.body = xml
595
+ end
596
+ unless @last_http_response.success?
597
+ handle_http_error
598
+ return
599
+ end
622
600
  @last_http_response.body
623
- rescue RestClient::ExceptionWithResponse => e
624
- handle_http_error e
625
601
  end # post_rsrc
626
602
 
627
603
  # Delete a resource from the JSS
@@ -630,18 +606,23 @@ module JSS
630
606
  #
631
607
  # @return [String] the xml response from the server.
632
608
  #
633
- def delete_rsrc(rsrc, xml = nil)
609
+ def delete_rsrc(rsrc)
634
610
  validate_connected
635
611
  raise MissingDataError, 'Missing :rsrc' if rsrc.nil?
636
612
 
637
- # payload?
638
- return delete_with_payload rsrc, xml if xml
639
-
640
613
  # delete the resource
641
- @last_http_response = @cnx[rsrc].delete
614
+ @last_http_response =
615
+ @cnx.delete(rsrc) do |req|
616
+ req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML
617
+ req.headers[HTTP_ACCEPT_HEADER] = MIME_XML
618
+ end
619
+
620
+ unless @last_http_response.success?
621
+ handle_http_error
622
+ return
623
+ end
624
+
642
625
  @last_http_response.body
643
- rescue RestClient::ExceptionWithResponse => e
644
- handle_http_error e
645
626
  end # delete_rsrc
646
627
 
647
628
  # Test that a given hostname & port is a JSS API server
@@ -657,25 +638,8 @@ module JSS
657
638
  # ssl_options like :OP_NO_SSLv2 and :OP_NO_SSLv3 will take time to figure out..
658
639
  return true if `/usr/bin/curl -s 'https://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
659
640
  return true if `/usr/bin/curl -s 'http://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
660
- false
661
641
 
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
642
+ false
679
643
  end
680
644
 
681
645
  # The server to which we are connected, or will
@@ -686,294 +650,13 @@ module JSS
686
650
  #
687
651
  def hostname
688
652
  return @server_host if @server_host
653
+
689
654
  srvr = JSS::CONFIG.api_server_name
690
655
  srvr ||= JSS::Client.jss_server
691
656
  srvr
692
657
  end
693
658
  alias host hostname
694
659
 
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
660
  # Empty all cached lists from this connection
978
661
  # then run garbage collection to clear any available memory
979
662
  #
@@ -1079,6 +762,7 @@ module JSS
1079
762
  #
1080
763
  def apply_defaults_from_client(args)
1081
764
  return unless JSS::Client.installed?
765
+
1082
766
  # these settings can come from the jamf binary config, if this machine is a JSS client.
1083
767
  args[:server] ||= JSS::Client.jss_server
1084
768
  args[:port] ||= JSS::Client.jss_port.to_i
@@ -1132,11 +816,13 @@ module JSS
1132
816
  # keep this basic level of info available for basic authentication
1133
817
  # and JSS version checking.
1134
818
  begin
1135
- @server = JSS::Server.new get_rsrc('jssuser')[:user], self
1136
- rescue RestClient::Unauthorized
819
+ data = get_rsrc('jssuser')
820
+ rescue JSS::AuthorizationError
1137
821
  raise JSS::AuthenticationError, "Incorrect JSS username or password for '#{@user}@#{@server_host}:#{@port}'."
1138
822
  end
1139
823
 
824
+ @server = JSS::Server.new data[:user], self
825
+
1140
826
  min_vers = JSS.parse_jss_version(JSS::MINIMUM_SERVER_VERSION)[:version]
1141
827
  return if @server.version >= min_vers # we're good...
1142
828
 
@@ -1205,77 +891,68 @@ module JSS
1205
891
  if SSL_PORTS.include? args[:port]
1206
892
  args[:use_ssl] = true unless args[:use_ssl] == false
1207
893
  end
894
+ return unless args[:use_ssl]
895
+
1208
896
  # 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
897
+ args[:verify_ssl] = args[:verify_cert] != false
898
+
899
+ # ssl version if not specified
900
+ args[:ssl_version] ||= DFT_SSL_VERSION
901
+
902
+ @ssl_options = {
903
+ verify: args[:verify_ssl],
904
+ version: args[:ssl_version]
905
+ }
1210
906
  end
1211
907
 
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
908
+ # Parses the @last_http_response
909
+ # and raises a JSS::APIError with a useful error message.
1218
910
  #
1219
911
  # @return [void]
1220
912
  #
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
913
+ def handle_http_error
914
+ return if @last_http_response.success?
915
+
916
+ case @last_http_response.status
917
+ when 404
918
+ err = JSS::NoSuchItemError
919
+ msg = 'Not Found'
920
+ when 409
1228
921
  err = JSS::ConflictError
1229
- msg_matcher = /<p>Error:(.*?)(<|$)/m
1230
- when RestClient::BadRequest
922
+ @last_http_response.body =~ /<p>(The server has not .*?)(<|$)/m
923
+ msg = Regexp.last_match(1)
924
+ when 400
1231
925
  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
926
+ @last_http_response.body =~ %r{>Bad Request</p>\n<p>(.*?)</p>\n<p>You can get technical detail}m
927
+ msg = Regexp.last_match(1)
928
+ when 401
929
+ err = JSS::AuthorizationError
930
+ msg = 'You are not authorized to do that.'
931
+ when (500..599)
932
+ err = JSS::APIRequestError
933
+ msg = 'There was an internal server error'
1235
934
  else
1236
935
  err = JSS::APIRequestError
1237
- msg_matcher = %r{<body.*?>(.*?)</body>}m
936
+ msg = "There was a error processing your request, status: #{@last_http_response.status}"
1238
937
  end
1239
- exception.http_body =~ msg_matcher
1240
- msg = Regexp.last_match(1)
1241
- msg ||= exception.http_body
1242
938
  raise err, msg
1243
939
  end
1244
940
 
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
941
+ # create the faraday connection object
942
+ def create_connection(pw)
943
+ Faraday.new(@rest_url, ssl: @ssl_options) do |cnx|
944
+ cnx.basic_auth @user, pw
945
+ cnx.options[:timeout] = @timeout
946
+ cnx.options[:open_timeout] = @open_timeout
947
+ cnx.adapter Faraday::Adapter::NetHttp
948
+ end
949
+ end
1276
950
 
1277
951
  end # class APIConnection
1278
952
 
953
+ # JSS MODULE METHODS
954
+ ######################
955
+
1279
956
  # Create a new APIConnection object and use it for all
1280
957
  # future API calls. If connection options are provided,
1281
958
  # they are passed to the connect method immediately, otherwise
@@ -1303,6 +980,7 @@ module JSS
1303
980
  #
1304
981
  def self.use_api_connection(connection)
1305
982
  raise 'API connections must be instances of JSS::APIConnection' unless connection.is_a? JSS::APIConnection
983
+
1306
984
  @api = connection
1307
985
  end
1308
986