ruby-jss 1.2.9 → 1.5.1

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 (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