ruby-jss 1.2.10 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

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