ruby-jss 1.2.4a3 → 1.2.4a4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ab1b92079de4a03997ab5fb60cf3463b63b7560cd45e83122ef52b563dec511
4
- data.tar.gz: bedaefb57524c8d8abc820f5078d4a4db005436c71b214818e7f1ca3f2acebaf
3
+ metadata.gz: 745114754cc7073bbf494e8eb54f48962c9cafff99da985108c90b5bc7ccc62c
4
+ data.tar.gz: 6455a7c53abc89c47a03b1bd9538bc0bd3375eab1776ca9c5a15425595ed2ed3
5
5
  SHA512:
6
- metadata.gz: dab434c14f91b160305f25bc8284a3e8731ae6edb842e9e600b667b46f53e126fc7a62e89c3875235a156d0b4b5628d143798e539ef196143d8d963bddb9fd1c
7
- data.tar.gz: b5ec8b9709cc9d9b32d4b609a39452d2e7d21b425db3fc36ba3140c5b111d7da084c1969ae27d8d96785cd2e958e5c6f803f7624b4d76fdc077149c249c423bb
6
+ metadata.gz: 10cd851d966cc1e966dec194b27a4f282bba25b80177886dc03d44429206c8bc8bb223f141c9da27547f096a439937c6db3ae0d7e6498d2d6570b5bca164f716
7
+ data.tar.gz: a194cd0e76b6264afc1deef0b2567169a83c7fba9aaad796f050171783df202d1fd3ccf04f5eb11606ea387badce68920822c698aab819bfb46a2fd2549a919f
data/README.md CHANGED
@@ -346,7 +346,7 @@ and then any calls to JSS.api.connect will assume that server and username, and
346
346
 
347
347
  The config files don't store passwords and the {JSS::Configuration} instance doesn't work with them. You'll have to use your own methods for acquiring the password for the JSS.api.connect call.
348
348
 
349
- The {JSS::API#connect} method also accepts the symbols :stdin# and :prompt as values for the :pw argument, which will cause it to read the password from a line of stdin, or prompt for it in the shell.
349
+ The {JSS::APIConnection.connect} method also accepts the symbols :stdin# and :prompt as values for the :pw argument, which will cause it to read the password from a line of stdin, or prompt for it in the shell.
350
350
 
351
351
  If you must store a password in a file, or retrieve it from the network, make sure it's stored securely, and that the JSS user has limited permissions.
352
352
 
@@ -168,6 +168,7 @@ module Jamf
168
168
  autoload :MobileDevicePrestage, 'jamf/api/resources/collection_resources/mobile_device_prestage'
169
169
  autoload :Site, 'jamf/api/resources/collection_resources/site'
170
170
  autoload :Script, 'jamf/api/resources/collection_resources/script'
171
+ autoload :TimeZone, 'jamf/api/resources/collection_resources/time_zone'
171
172
 
172
173
  # other classes used as attributes inside the resource classes
173
174
  autoload :IPAddress, 'jamf/api/attribute_classes/ip_address'
@@ -100,7 +100,11 @@ module Jamf
100
100
  def self.all(refresh = false, cnx: Jamf.cnx, instantiate: false)
101
101
  validate_not_abstract
102
102
  cnx.collection_cache[self] = nil if refresh
103
- return cnx.collection_cache[self] if cnx.collection_cache[self]
103
+ if cnx.collection_cache[self]
104
+ return cnx.collection_cache[self] unless instantiate
105
+
106
+ return cnx.collection_cache[self].map { |m| new m }
107
+ end
104
108
 
105
109
  raw = cnx.get rsrc_path
106
110
  cnx.collection_cache[self] =
@@ -135,9 +139,10 @@ module Jamf
135
139
  # identifier and values are any other attribute.
136
140
  #
137
141
  # @param ident [Symbol] An identifier of this Class, used as the key
138
- # for the mapping Hash.
142
+ # for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber
139
143
  #
140
- # @param to [Symbol] The attribute to which the ident will be mapped
144
+ # @param to [Symbol] The attribute to which the ident will be mapped.
145
+ # Aliases are acceptable, e.g. :name for :displayName
141
146
  #
142
147
  # @param refresh (see .all)
143
148
  #
@@ -146,11 +151,15 @@ module Jamf
146
151
  # @return [Hash {Symbol: Object}] A Hash of identifier mapped to attribute
147
152
  #
148
153
  def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false)
154
+ real_ident = attr_key_for_alias ident
149
155
  raise Jamf::InvalidDataError, "No identifier #{ident} for class #{self}" unless
150
- identifiers.include? ident
156
+ identifiers.include? real_ident
151
157
 
152
- raise Jamf::NoSuchItemError, "No attribute #{to} for class #{self}" unless self::OBJECT_MODEL.key? to
158
+ real_to = attr_key_for_alias to
159
+ raise Jamf::NoSuchItemError, "No attribute #{to} for class #{self}" unless self::OBJECT_MODEL.key? real_to
153
160
 
161
+ ident = real_ident
162
+ to = real_to
154
163
  list = all refresh, cnx: cnx
155
164
  to_class = self::OBJECT_MODEL[to][:class]
156
165
  mapped = list.map do |i|
@@ -163,13 +172,15 @@ module Jamf
163
172
  end
164
173
  # rubocop:enable Naming/UncommunicativeMethodParamName
165
174
 
166
- # Given any identfier value for this collection, return the valid
167
- # id, or nil if there's no match for the given value.
175
+ # Given any identfier value for this collection, return the id of the
176
+ # object that has such an identifier.
177
+ #
178
+ # Return nil if there's no match for the given value.
168
179
  #
169
- # If you know the value is a certain identifier, e.g. a serial_number
170
- # then you can specify the identifier, for a faster search:
180
+ # If you know the value is a certain identifier, e.g. a serialNumber,
181
+ # then you can specify the identifier for a faster search:
171
182
  #
172
- # valid_id serial_number: 'AB12DE34' # => Int or nil
183
+ # valid_id serialNumber: 'AB12DE34' # => Int or nil
173
184
  #
174
185
  # If you don't know wich identifier you have, just pass the value and
175
186
  # all identifiers are searched
@@ -188,7 +199,7 @@ module Jamf
188
199
  # @param refresh[Boolean] Reload the list data from the API
189
200
  #
190
201
  # @param ident: [Symbol] Restrict the search to this identifier.
191
- # E.g. if :serial_number, then the value must be
202
+ # E.g. if :serialNumber, then the value must be
192
203
  # a known serial number, it is not checked against other identifiers
193
204
  #
194
205
  # @param cnx: (see .all)
@@ -375,7 +386,7 @@ module Jamf
375
386
  ident_map = map_all(ident, to: :id, cnx: cnx, refresh: refresh)
376
387
 
377
388
  # case-insensitivity for string values
378
- value = ident_map.keys.j_ci_fetch_string(value) if value.is_a? String
389
+ value = ident_map.keys.j_ci_fetch(value) if value.is_a? String
379
390
 
380
391
  ident_map[value]
381
392
  end
@@ -727,7 +727,7 @@ module Jamf
727
727
 
728
728
  attr_def[:aliases].each { |al| alias_method "#{al}_delete_if", "#{attr_name}_delete_if" }
729
729
  end # create_insert_setters
730
- private_class_method :create_delete_at_setters
730
+ private_class_method :create_delete_if_setters
731
731
 
732
732
  # Raise an exception if this is an abstract class
733
733
  # Used in class methods that are defined in abstract classes.
@@ -762,13 +762,9 @@ module Jamf
762
762
  #
763
763
  # Otherwise, the value is returned unchanged.
764
764
  #
765
- # If the attribute is defined as an identifier, it must be unique among
766
- # the other objects of this subclass in the JSS.
767
- #
768
765
  # This method only validates single values. When called from multi-value
769
766
  # setters, it is used for each value individually.
770
767
  #
771
- #
772
768
  # @param attr_name[Symbol], a top-level key from OBJECT_MODEL for this class
773
769
  #
774
770
  # @param value [Object] the value to validate for that attribute.
@@ -241,11 +241,11 @@ module Jamf
241
241
  end
242
242
 
243
243
  # The id of the prestage to which the given serialNumber is assigned.
244
- # nil if not assigned
244
+ # nil if not assigned or not in DEP.
245
245
  #
246
246
  # NOTE: If a serial number isn't assigned to any prestage, it may really be
247
- # unassigned or it may not exist in your DEP. At the moment there's no way
248
- # via the JP-API to know the SNs in DEP that are not assigned
247
+ # unassigned or it may not exist in your DEP. To see if a SN exists in one
248
+ # of your Device Enrollment instances, use Jamf::DeviceEnrollment.include?
249
249
  #
250
250
  # @param sn [String] the serial number to look for
251
251
  #
@@ -263,8 +263,8 @@ module Jamf
263
263
  # given prestage if a prestage_ident is specified?
264
264
  #
265
265
  # NOTE: If a serial number isn't assigned to any prestage, it may really be
266
- # unassigned or it may not exist in your DEP. At the moment there's no way
267
- # via the JP-API to know the SNs in DEP but not assigned
266
+ # unassigned or it may not exist in your DEP. To see if a SN exists in one
267
+ # of your Device Enrollment instances, use Jamf::DeviceEnrollment.include?
268
268
  #
269
269
  # @param sn [String] the serial number to look for
270
270
  #
@@ -283,10 +283,10 @@ module Jamf
283
283
  return false unless assigned_id
284
284
 
285
285
  if prestage_ident
286
- id = valid_id prestage_ident, cnx: cnx
287
- raise Jamf::NoSuchItemError, "No #{self} matching '#{prestage_ident}'" unless id
286
+ psid = valid_id prestage_ident, cnx: cnx
287
+ raise Jamf::NoSuchItemError, "No #{self} matching '#{prestage_ident}'" unless psid
288
288
 
289
- return id == assigned_id
289
+ return psid == assigned_id
290
290
  end
291
291
 
292
292
  true
@@ -305,13 +305,84 @@ module Jamf
305
305
  Jamf::DeviceEnrollment.device_sns(type: type, cnx: cnx) - serials_by_prestage_id(:refresh, cnx: cnx).keys
306
306
  end
307
307
 
308
- # @return [Array<String>] The serial numbers of devices that are not in DEP
308
+ # @return [Array<String>] The serial numbers of known hardware not in DEP
309
309
  # at all
310
310
  def self.sns_not_in_device_enrollment
311
311
  # type = self == Jamf::MobileDevicePrestage ? :mobiledevices : :computers
312
- nil # TODO: this, once MobileDevice is implemented
312
+ nil # TODO: this, once MobileDevice & Computer classes are implemented
313
313
  end
314
314
 
315
+ # Assign one or more serialNumber to a prestage
316
+ # @return [Jamf::PrestageScope] the new scope for the prestage
317
+ def self.assign(*sns_to_assign, to_prestage:, cnx: Jamf.cnx)
318
+ prestage_id = valid_id to_prestage
319
+ raise Jamf::NoSuchItemError, "No #{self} matching '#{to_prestage}'" unless prestage_id
320
+
321
+ # all sns_to_assign must be in DEP
322
+ not_in_dep = sns_to_assign - Jamf::DeviceEnrollment.device_sns
323
+ raise Jamf::UnsupportedError, "These SNs are not in any Device Enrollment instance: #{not_in_dep.join ', '}" unless not_in_dep.empty?
324
+
325
+ # all sns_to_assign must currently be unassigned.
326
+ already_assigned = sns_to_assign - unassigned_sns
327
+ raise Jamf::UnsupportedError, "These SNs are already assigned to a prestage: #{already_assigned.join ', '}" unless already_assigned.empty?
328
+
329
+ # upcase all sns
330
+ sns_to_assign.map!(&:to_s)
331
+ sns_to_assign.map!(&:upcase)
332
+
333
+ # get the prestage name
334
+ prestage_name = map_all(:id, to: :displayName)[prestage_id]
335
+
336
+ scope_rsrc = "#{self::RSRC_VERSION}/#{self::RSRC_PATH}/#{prestage_id}/#{SCOPE_RSRC}"
337
+ scope = Jamf::PrestageScope.new cnx.get(scope_rsrc)
338
+
339
+ # add the new sns to the existing ones
340
+ new_scope_sns = scope.assignments.map(&:serialNumber)
341
+ new_scope_sns += sns_to_assign
342
+ new_scope_sns.uniq!
343
+
344
+ update_scope(prestage_name, scope_rsrc, new_scope_sns, scope.versionLock, cnx)
345
+ end # self.assign
346
+
347
+ # Unassign one or more serialNumber from a prestage
348
+ # @return [Jamf::PrestageScope] the new scope for the prestage
349
+ def self.unassign(*sns_to_unassign, from_prestage:, cnx: Jamf.cnx)
350
+ prestage_id = valid_id from_prestage
351
+ raise Jamf::NoSuchItemError, "No #{self} matching '#{from_prestage}'" unless prestage_id
352
+
353
+ # upcase all sns
354
+ sns_to_unassign.map!(&:to_s)
355
+ sns_to_unassign.map!(&:upcase)
356
+
357
+ # get the prestage name
358
+ prestage_name = map_all(:id, to: :displayName)[prestage_id]
359
+
360
+ scope_rsrc = "#{self::RSRC_VERSION}/#{self::RSRC_PATH}/#{prestage_id}/#{SCOPE_RSRC}"
361
+ scope = Jamf::PrestageScope.new cnx.get(scope_rsrc)
362
+
363
+ new_scope_sns = scope.assignments.map(&:serialNumber)
364
+ new_scope_sns -= sns_to_unassign
365
+
366
+ update_scope(prestage_name, scope_rsrc, new_scope_sns, scope.versionLock, cnx)
367
+ end # self.unassign
368
+
369
+ # Provate Class Methods
370
+ #####################################
371
+
372
+ # used by assign and unassign
373
+ def self.update_scope(prestage_name, scope_rsrc, new_scope_sns, vlock, cnx)
374
+ assignment_data = {
375
+ serialNumbers: new_scope_sns,
376
+ versionLock: vlock
377
+ }
378
+ Jamf::PrestageScope.new cnx.put(scope_rsrc, assignment_data)
379
+ rescue Jamf::Connection::APIError => e
380
+ raise Jamf::VersionLockError, "The #{self} '#{prestage_name}' was modified by another process during this operation. Please try again" if e.status == 409
381
+
382
+ raise e
383
+ end
384
+ private_class_method :update_scope
385
+
315
386
  # Instance Methods
316
387
  #####################################
317
388
 
@@ -351,19 +422,24 @@ module Jamf
351
422
 
352
423
  # Assign
353
424
  def assign(*sns_to_assign)
354
- sns_to_assign.map!(&:to_s)
355
- new_scope_sns = assigned_sns
356
- new_scope_sns += sns_to_assign
357
- new_scope_sns.uniq!
358
- update_scope(new_scope_sns)
425
+ @scope = self.class.assign(sns_to_assign, to_prestage: @id, cnx: @cnx)
426
+ @versionLock = @scope.versionLock
427
+
428
+ # sns_to_assign.map!(&:to_s)
429
+ # new_scope_sns = assigned_sns
430
+ # new_scope_sns += sns_to_assign
431
+ # new_scope_sns.uniq!
432
+ # update_scope(new_scope_sns)
359
433
  end
360
434
  alias add assign
361
435
 
362
436
  def unassign(*sns_to_unassign)
363
- sns_to_unassign.map!(&:to_s)
364
- new_scope_sns = assigned_sns
365
- new_scope_sns -= sns_to_unassign
366
- update_scope(new_scope_sns)
437
+ @scope = self.class.unassign(sns_to_unassign, from_prestage: @id, cnx: @cnx)
438
+ @versionLock = @scope.versionLock
439
+ # sns_to_unassign.map!(&:to_s)
440
+ # new_scope_sns = assigned_sns
441
+ # new_scope_sns -= sns_to_unassign
442
+ # update_scope(new_scope_sns)
367
443
  end
368
444
  alias remove unassign
369
445
 
@@ -382,20 +458,20 @@ module Jamf
382
458
  @scope_rsrc ||= "#{self.class::RSRC_VERSION}/#{self.class::RSRC_PATH}/#{@id}/#{SCOPE_RSRC}"
383
459
  end
384
460
 
385
- def update_scope(new_scope_sns)
386
- assignment_data = {
387
- serialNumbers: new_scope_sns,
388
- versionLock: @scope.versionLock
389
- }
390
- begin
391
- @scope = Jamf::PrestageScope.new @cnx.put(scope_rsrc, assignment_data)
392
- rescue Jamf::Connection::APIError => e
393
- raise Jamf::VersionLockError, "The #{self.class} '#{name}' has been modified since it was fetched. Please refetch and try again" if e.status == 409
394
-
395
- raise e
396
- end # begin
397
- @versionLock = @scope.versionLock
398
- end
461
+ # def update_scope(new_scope_sns)
462
+ # assignment_data = {
463
+ # serialNumbers: new_scope_sns,
464
+ # versionLock: @scope.versionLock
465
+ # }
466
+ # begin
467
+ # @scope = Jamf::PrestageScope.new @cnx.put(scope_rsrc, assignment_data)
468
+ # rescue Jamf::Connection::APIError => e
469
+ # raise Jamf::VersionLockError, "The #{self.class} '#{name}' has been modified since it was fetched. Please refetch and try again" if e.status == 409
470
+ #
471
+ # raise e
472
+ # end # begin
473
+ # @versionLock = @scope.versionLock
474
+ # end
399
475
 
400
476
  end # class
401
477
 
@@ -77,17 +77,17 @@ module Jamf
77
77
  DFT_SSL_VERSION = 'TLSv1_2'.freeze
78
78
 
79
79
  # refresh token if less than this many seconds until
80
- # expiration. Default is 30 minutes if not specified
81
- DFT_TOKEN_REFRESH = 60 * 30
80
+ # expiration. Default is 5 minutes if not specified
81
+ DFT_TOKEN_REFRESH = 300
82
82
 
83
83
  # pre-existing tokens must have this many seconds before
84
84
  # before they expire
85
85
  TOKEN_REUSE_MIN_LIFE = 60
86
86
 
87
- HTTP_ACCEPT_HEADER = 'Accept'
88
- HTTP_CONTENT_TYPE_HEADER = 'Content-Type'
87
+ HTTP_ACCEPT_HEADER = 'Accept'.freeze
88
+ HTTP_CONTENT_TYPE_HEADER = 'Content-Type'.freeze
89
89
 
90
- MIME_JSON = 'application/json'
90
+ MIME_JSON = 'application/json'.freeze
91
91
 
92
92
  SLASH = '/'.freeze
93
93
 
@@ -316,8 +316,11 @@ module Jamf
316
316
  end # connect
317
317
 
318
318
  def disconnect
319
- # reset everything except the name & timeouts
319
+ # reset everything except the timeouts
320
+ stop_keep_alive
321
+
320
322
  @connected = false
323
+ @name = NOT_CONNECTED
321
324
  @login_time = nil
322
325
  @host = nil
323
326
  @port = nil
@@ -327,6 +330,7 @@ module Jamf
327
330
  @rest_cnx = nil
328
331
  @ssl_options = {}
329
332
  @keep_alive = nil
333
+
330
334
  flushcache
331
335
  end
332
336
 
@@ -342,7 +346,7 @@ module Jamf
342
346
  @last_http_response = resp
343
347
  return resp.body if resp.success?
344
348
 
345
- raise Jamf::Connection::APIError.new(resp)
349
+ raise Jamf::Connection::APIError, resp
346
350
  end
347
351
 
348
352
  # GET a rsrc without doing any JSON parsing, using
@@ -353,7 +357,7 @@ module Jamf
353
357
  @last_http_response = resp
354
358
  return resp.body if resp.success?
355
359
 
356
- raise Jamf::Connection::APIError.new(resp)
360
+ raise Jamf::Connection::APIError, resp
357
361
  end
358
362
 
359
363
  def post(rsrc, data)
@@ -364,7 +368,7 @@ module Jamf
364
368
  @last_http_response = resp
365
369
  return resp.body if resp.success?
366
370
 
367
- raise Jamf::Connection::APIError.new(resp)
371
+ raise Jamf::Connection::APIError, resp
368
372
  end
369
373
 
370
374
  def put(rsrc, data)
@@ -375,7 +379,7 @@ module Jamf
375
379
  @last_http_response = resp
376
380
  return resp.body if resp.success?
377
381
 
378
- raise Jamf::Connection::APIError.new(resp)
382
+ raise Jamf::Connection::APIError, resp
379
383
  end
380
384
 
381
385
  def patch(rsrc, data)
@@ -386,7 +390,7 @@ module Jamf
386
390
  @last_http_response = resp
387
391
  return resp.body if resp.success?
388
392
 
389
- raise Jamf::Connection::APIError.new(resp)
393
+ raise Jamf::Connection::APIError, resp
390
394
  end
391
395
 
392
396
  def delete(rsrc)
@@ -395,7 +399,7 @@ module Jamf
395
399
  @last_http_response = resp
396
400
  return resp.body if resp.success?
397
401
 
398
- raise Jamf::Connection::APIError.new(resp)
402
+ raise Jamf::Connection::APIError, resp
399
403
  end
400
404
 
401
405
  # A useful string about this connection
@@ -403,13 +407,48 @@ module Jamf
403
407
  # @return [String]
404
408
  #
405
409
  def to_s
406
- "Jamf::Connection: https://#{@user}@#{@host}:#{@port}"
410
+ str =
411
+ if connected?
412
+ "#{@base_url}, Token expires: #{@token ? @token.expires : '<token missing>'}"
413
+ else
414
+ NOT_CONNECTED
415
+ end
416
+ "Jamf::Connection '#{@name == NOT_CONNECTED ? object_id : @name}': #{str}"
417
+ end
418
+
419
+ # Reset the name
420
+ def name=(new_name)
421
+ @name = new_name.to_s
407
422
  end
408
423
 
424
+ # Are we keeping the connection alive?
409
425
  def keep_alive?
410
- !@keep_alive_thread.nil?
426
+ @keep_alive_thread&.alive? || false
427
+ end
428
+
429
+ # @return [Jamf::Timestamp, nil]
430
+ def next_refresh
431
+ return unless keep_alive?
432
+
433
+ @token.expires - @token_refresh
434
+ end
435
+
436
+ # @return [Float, nil]
437
+ def secs_to_refresh
438
+ return unless keep_alive?
439
+
440
+ next_refresh - Time.now
441
+ end
442
+
443
+ # @return [String, nil] e.g. "1 week 6 days 23 hours 49 minutes 56 seconds"
444
+ def time_to_refresh
445
+ return unless keep_alive?
446
+ return 0 if secs_to_refresh.negative?
447
+
448
+ Jamf.humanize_secs secs_to_refresh
411
449
  end
412
450
 
451
+ # Turn keepalive on or offs
413
452
  def keep_alive=(bool)
414
453
  bool ? start_keep_alive : stop_keep_alive
415
454
  end
@@ -458,7 +497,6 @@ module Jamf
458
497
  ####################################
459
498
  private
460
499
 
461
-
462
500
  # raise exception if not connected
463
501
  def validate_connected
464
502
  raise Jamf::InvalidConnectionError, 'Not Connected. Use .connect first.' unless connected?
@@ -619,7 +657,11 @@ module Jamf
619
657
  @port = params[:port]
620
658
  @port ||= @host.end_with?(JAMFCLOUD_DOMAIN) ? JAMFCLOUD_PORT : ON_PREM_SSL_PORT
621
659
  @user = params[:user]
622
- @token_refresh = params[:token_refresh] || DFT_TOKEN_REFRESH
660
+
661
+ @token_refresh = params[:token_refresh].to_i || DFT_TOKEN_REFRESH
662
+ # token refresh must be at least DFT_TOKEN_REFRESH
663
+ @token_refresh = DFT_TOKEN_REFRESH if @token_refresh < DFT_TOKEN_REFRESH
664
+
623
665
  @timeout = params[:timeout] || DFT_TIMEOUT
624
666
  @open_timeout = params[:open_timeout] || DFT_TIMEOUT
625
667
  @base_url = URI.parse "https://#{@host}:#{@port}/#{RSRC_BASE}"
@@ -702,9 +744,16 @@ module Jamf
702
744
  Thread.new do
703
745
  loop do
704
746
  sleep 60
705
- next if @token.secs_remaining > @token_refresh
706
-
707
- @token.keep_alive
747
+ begin
748
+ next if @token.secs_remaining > @token_refresh
749
+
750
+ @token.refresh
751
+ # make sure faraday uses the new token
752
+ @rest_cnx.headers[:authorization] = @token.auth_token
753
+ rescue
754
+ # TODO: Some kind of error reporting??
755
+ next
756
+ end
708
757
  end # loop
709
758
  end # thread
710
759
  end
@@ -715,9 +764,7 @@ module Jamf
715
764
  # @return [void]
716
765
  #
717
766
  def stop_keep_alive
718
- return unless @keep_alive_thread
719
-
720
- @keep_alive_thread.kill
767
+ @keep_alive_thread&.kill
721
768
  @keep_alive_thread = nil
722
769
  end
723
770
 
@@ -763,4 +810,8 @@ module Jamf
763
810
  @active_connection = connection
764
811
  end
765
812
 
813
+ def self.disconnect
814
+ @active_connection&.disconnect
815
+ end
816
+
766
817
  end # module Jamf
@@ -119,24 +119,27 @@ module Jamf
119
119
 
120
120
  # @return [String]
121
121
  def api_version
122
- token_connection(Jamf::Connection::SLASH, token: @auth_token ).get.body[:version]
122
+ token_connection(Jamf::Connection::SLASH, token: @auth_token).get.body[:version]
123
123
  end
124
124
 
125
125
  # @return [Boolean]
126
126
  def expired?
127
127
  return unless @expires
128
+
128
129
  Time.now >= @expires
129
130
  end
130
131
 
131
132
  # @return [Float]
132
133
  def secs_remaining
133
134
  return unless @expires
135
+
134
136
  @expires - Time.now
135
137
  end
136
138
 
137
139
  # @return [String] e.g. "1 week 6 days 23 hours 49 minutes 56 seconds"
138
140
  def time_remaining
139
141
  return unless @expires
142
+
140
143
  Jamf.humanize_secs secs_remaining
141
144
  end
142
145
 
@@ -155,18 +158,20 @@ module Jamf
155
158
  # the Jamf::Account object assciated with this token
156
159
  def account
157
160
  return @account if @account
161
+
158
162
  resp = token_connection(AUTH_RSRC, token: @auth_token).get
159
163
  return unless resp.success?
160
164
 
161
- @account = Jamf::APIAccount.new resp.body
165
+ @account = Jamf::APIAccount.new resp.body
162
166
  end
163
167
 
164
168
  # Use this token to get a fresh one
169
+ # TODO: better error reporting
165
170
  def refresh
166
171
  raise 'Token has expired' if expired?
167
172
 
168
173
  keep_alive_token_resp = token_connection(KEEP_ALIVE_RSRC, token: @auth_token).post
169
- # TODO: better error reporting here
174
+
170
175
  raise 'An error occurred while authenticating' unless keep_alive_token_resp.success?
171
176
 
172
177
  parse_token_from_response keep_alive_token_resp
@@ -189,8 +194,7 @@ module Jamf
189
194
  # acquision & manipulation
190
195
  #
191
196
  def token_connection(rsrc, token: nil, pw: nil, timeout: nil, ssl_opts: nil)
192
-
193
- Faraday.new("#{@base_url}/#{rsrc}", ssl: ssl_opts ) do |con|
197
+ Faraday.new("#{@base_url}/#{rsrc}", ssl: ssl_opts) do |con|
194
198
  con.headers[Jamf::Connection::HTTP_ACCEPT_HEADER] = Jamf::Connection::MIME_JSON
195
199
  con.response :json, parser_options: { symbolize_names: true }
196
200
  con.options[:timeout] = timeout
@@ -77,10 +77,13 @@ module Jamf
77
77
  # @!attribute deviceEnrollmentProgramInstanceId
78
78
  # @return [Integer]
79
79
  deviceEnrollmentProgramInstanceId: {
80
- class: :integer
80
+ class: :integer,
81
+ aliases: %i[instanceId]
81
82
  },
82
83
 
83
84
  # @!attribute prestageId
85
+ # The most recent prestage this device was assigned to, even if
86
+ # currently unassigned to any prestage.
84
87
  # @return [Integer]
85
88
  prestageId: {
86
89
  class: :integer
@@ -135,6 +138,7 @@ module Jamf
135
138
  },
136
139
 
137
140
  # @!attribute deviceAssignedDate
141
+ # When Apple assigned this device to this DevEnrollment instance
138
142
  # @return [Jamf::Timestamp]
139
143
  deviceAssignedDate: {
140
144
  class: Jamf::Timestamp
@@ -146,8 +150,6 @@ module Jamf
146
150
  # Class Methods
147
151
  #########################################
148
152
 
149
-
150
-
151
153
  # Instance Methods
152
154
  #########################################
153
155
 
@@ -26,7 +26,8 @@
26
26
  # The module
27
27
  module Jamf
28
28
 
29
- # A 'location' for a managed object in Jamf Pro
29
+ # An assignment of a device to a prestage, placing that
30
+ # device into the prestage's scope
30
31
  class PrestageAssignment < Jamf::JSONObject
31
32
 
32
33
  extend Jamf::Immutable
@@ -48,7 +49,8 @@ module Jamf
48
49
  # @!attribute userAssigned
49
50
  # @return [String]
50
51
  userAssigned: {
51
- class: :string
52
+ class: :string,
53
+ aliases: %i[assignedBy]
52
54
  }
53
55
  }.freeze
54
56
 
@@ -60,6 +60,8 @@ module Jamf
60
60
  #
61
61
  module ChangeLog
62
62
 
63
+ # TODO: note can have a max length of 2500 characters.
64
+
63
65
  # The change and note history for this resource.
64
66
  #
65
67
  # The history is cached internally and only re-fetched when
@@ -161,7 +161,7 @@ module Jamf
161
161
  enum: SORT_DIRECTIONS
162
162
  }
163
163
  }.freeze
164
-
164
+ parse_object_model
165
165
  end # class Orderby
166
166
 
167
167
  class SeachParams < Jamf::JSONObject
@@ -194,7 +194,7 @@ module Jamf
194
194
  }
195
195
 
196
196
  }.freeze
197
-
197
+ parse_object_model
198
198
  end # class SearchParams
199
199
 
200
200
  end # module searchable
@@ -71,6 +71,7 @@ module Jamf
71
71
  required: true
72
72
  }
73
73
  }.freeze
74
+
74
75
  parse_object_model
75
76
 
76
77
 
@@ -144,6 +144,8 @@ module Jamf
144
144
 
145
145
  LATEST_RSRC = 'latest'.freeze
146
146
 
147
+ DISOWN_RSRC = 'disown'.freeze
148
+
147
149
  TYPES = %i[computers mobiledevices].freeze
148
150
 
149
151
  COMPUTERS_RE = /mac/i.freeze
@@ -152,7 +154,12 @@ module Jamf
152
154
  #########################################
153
155
 
154
156
  # All devices associated by Apple with a given DeviceEnrollment instance
155
- # or all defined DeviceEnrollment instances
157
+ # or all defined DeviceEnrollment instances.
158
+ #
159
+ # This data is cached the first time it is read from the API, similarly to
160
+ # how CollectionResources are cached. To refresh the cache, pass
161
+ # a truthy value to the refresh: parameter, or use the Connection's
162
+ # .flushcache method
156
163
  #
157
164
  # @param instance_ident[Integer, String] the id or name of the
158
165
  # DeviceEnrollment instance for which to list the devices. If omitted,
@@ -161,17 +168,19 @@ module Jamf
161
168
  # @param type[Symbol] Either :computers or :mobiledevices, returns both if
162
169
  # not specified.
163
170
  #
171
+ # @param refresh [Boolean] re-read the data from the API?
172
+ #
164
173
  # @param cnx[Jamf::Connection] The API connection to use
165
174
  #
166
175
  # @return [Array<Jamf::DeviceEnrollmentDevice>] The devices associated with
167
- # the given, or all, instances
176
+ # the given DeviceEnrollment instance, or all instances
168
177
  #
169
- def self.devices(instance_ident = nil, type: nil, cnx: Jamf.cnx)
178
+ def self.devices(instance_ident = nil, type: nil, refresh: false, cnx: Jamf.cnx)
170
179
  if type
171
180
  raise ArgumentError, "Type must be one of: :#{TYPES.join ', :'}" unless TYPES.include? type
172
181
  end
173
182
 
174
- devs = fetch_devices(instance_ident, cnx)
183
+ devs = fetch_devices(instance_ident, refresh, cnx)
175
184
  return devs unless type
176
185
 
177
186
  if type == :computers
@@ -181,23 +190,33 @@ module Jamf
181
190
  end
182
191
  end
183
192
 
193
+ # The serial numbers assigned bu Apple to one, or all of your
194
+ # Device Enrollment instances
195
+ #
184
196
  # See .devices
197
+ #
185
198
  # @return [Array<String>] just the serial numbers for the devices
186
199
  #
187
- def self.device_sns(instance_ident = nil, type: nil, cnx: Jamf.cnx)
188
- devices(instance_ident, type: type, cnx: cnx).map(&:serialNumber)
200
+ def self.device_sns(instance_ident = nil, type: nil, refresh: false, cnx: Jamf.cnx)
201
+ devices(instance_ident, type: type, refresh: refresh, cnx: cnx).map(&:serialNumber)
189
202
  end
190
203
 
204
+ # Is the given serial number in one, or any, or your Device Enrollment
205
+ # instances?
206
+ #
191
207
  # See .devices
192
208
  #
209
+ # @param sn [String] the serialNumber to look for
210
+ #
193
211
  # @return [Boolean] is the given SN in a given DeviceEnrollment instance
194
212
  # or in DEP at all?
195
213
  #
196
- def self.include?(sn, instance_ident = nil, type: nil, cnx: Jamf.cnx)
197
- device_sns(instance_ident, type: type, cnx: cnx).j_ci_include? sn
214
+ def self.include?(sn, instance_ident = nil, type: nil, refresh: false, cnx: Jamf.cnx)
215
+ device_sns(instance_ident, type: type, refresh: refresh, cnx: cnx).j_ci_include? sn
198
216
  end
199
217
 
200
218
  # See .devices
219
+ #
201
220
  # Returns just those devices with the desired profileStatus, which must be
202
221
  # an item in DeviceEnrollmentDevice::PROFILE_STATUSES
203
222
  #
@@ -206,14 +225,54 @@ module Jamf
206
225
  # @return [Array<Jamf::DeviceEnrollmentDevice>] The devices with the desired
207
226
  # status, associated with the given, or all, instances
208
227
  #
209
- def self.devices_with_status(status, instance_ident = nil, type: nil, cnx: Jamf.cnx)
228
+ def self.devices_with_status(status, instance_ident = nil, type: nil, refresh: false, cnx: Jamf.cnx)
210
229
  unless Jamf::DeviceEnrollmentDevice::PROFILE_STATUSES.include? status
211
230
  raise ArgumentError, "profileStatus must be one of: '#{Jamf::DeviceEnrollmentDevice::PROFILE_STATUSES.join "', '"}'"
212
231
  end
213
232
 
214
- devices(instance_ident, type: type, cnx: cnx).select { |d| d.profileStatus == status }
233
+ devices(instance_ident, type: type, refresh: refresh, cnx: cnx).select { |d| d.profileStatus == status }
215
234
  end
216
235
 
236
+ # Fetch a single device from any defined DeviceEnrollment instance.
237
+ # The instance id containing the device is available in its
238
+ # .deviceEnrollmentProgramInstanceId attribute.
239
+ #
240
+ # @pararm sn [String] the serial number of the device
241
+ #
242
+ # @param instance_ident [String, Integer] the name or id of the instance
243
+ # in which to look for the sn. All instances are searched if omitted.
244
+ #
245
+ # @param refresh [Boolean] re-read the data from the API?
246
+ #
247
+ # @param cnx[Jamf::Connection] The API connection to use
248
+ #
249
+ # @return [Jamf::DeviceEnrollmentDevice] the device as known to DEP
250
+ #
251
+ def self.device(sn, instance_ident = nil, refresh: false, cnx: Jamf.cnx)
252
+ sn.upcase! # SNs from apple are always uppercase
253
+ devs = devices(instance_ident, refresh: refresh, cnx: cnx)
254
+ dev = devs.select { |d| d.serialNumber == sn }.first
255
+ return dev if dev
256
+
257
+ searched = instance_ident ? "DeviceEnrollment instance #{instance_ident}" : 'any DeviceEnrollment instance'
258
+ raise Jamf::NoSuchItemError, "No device with serialNumber '#{sn}' in #{searched}"
259
+ end
260
+
261
+ # The history of sync operations between Apple and a given DeviceEnrollment
262
+ # instanace, or all instances.
263
+ #
264
+ # @param instance_ident[Integer, String] the id or name of the
265
+ # DeviceEnrollment instance for which to get the history. If omitted,
266
+ # the history for all instances will be returned.
267
+ #
268
+ # @param latest [Boolean] show only the latest sync? Only valid when an
269
+ # instance_ident is provided.
270
+ #
271
+ # @param cnx[Jamf::Connection] The API connection to use
272
+ #
273
+ # @return [Jamf::DeviceEnrollmentSyncStatus] When latest = true, the latest
274
+ # sync status.
275
+ # @return [Array<Jamf::DeviceEnrollmentSyncStatus>] The known sync statuses.
217
276
  #
218
277
  def self.sync_history(instance_ident = nil, latest = false, cnx: Jamf.cnx)
219
278
  if instance_ident
@@ -232,31 +291,60 @@ module Jamf
232
291
  data.map! { |s| Jamf::DeviceEnrollmentSyncStatus.new s }
233
292
  end
234
293
 
294
+ # disown one or more serial numbers from a given DeviceEnrollment instance
295
+ #
296
+ # @param sns[Array<String>] One or more serial numbers to disown
297
+ #
298
+ # @param from_instance [Integer, String] the id or name of the instance
299
+ # from which to disown the serial numbers
300
+ #
301
+ # @param cnx[Jamf::Connection] The API connection to use
302
+ #
303
+ # @return [void]
304
+ #
305
+ def self.disown(*sns, from_instance:, cnx: Jamf.cnx)
306
+ instance_id = valid_id from_instance, cnx: cnx
307
+ raise Jamf::NoSuchItemError, "No DeviceEnrollment instance matches '#{instance}'" unless instance_id
308
+
309
+ sns.flatten!
310
+ sns.map!(&:to_s)
311
+ data = { devices: sns }
312
+ disown_rsrc = "#{self.class::RSRC_VERSION}/#{self.class::RSRC_PATH}/#{instance_id}/#{DISOWN_RSRC}"
313
+
314
+ cnx.post(disown_rsrc, data)
315
+ end
316
+
235
317
  # Private Class Methods
236
318
  ###############################################
237
319
 
238
320
  # Private, used by the .devices class method
239
- def self.fetch_devices(instance_ident, cnx)
321
+ def self.fetch_devices(instance_ident, refresh, cnx)
240
322
  if instance_ident
241
323
  instance_id = valid_id instance_ident, cnx: cnx
242
- raise Jamf::NoSuchItemError "No DeviceEnrollment instance matches '#{instance_ident}'" unless instance_id
324
+ raise Jamf::NoSuchItemError, "No DeviceEnrollment instance matches '#{instance_ident}'" unless instance_id
243
325
 
244
- devs = devices_for_instance_id instance_id, cnx
326
+ devs = devices_for_instance_id instance_id, refresh, cnx
245
327
  else
246
328
  devs = []
247
329
  all_ids.each do |id|
248
- devs += devices_for_instance_id id, cnx
330
+ devs += devices_for_instance_id id, refresh, cnx
249
331
  end
250
332
  end
251
333
  devs
252
334
  end
253
335
  private_class_method :fetch_devices
254
336
 
255
- # Private, used by the .devices class method
256
- def self.devices_for_instance_id(instance_id, cnx)
337
+ # Private, used by the .fetch_devices class method
338
+ def self.devices_for_instance_id(instance_id, refresh, cnx)
339
+ @device_cache ||= {}
340
+ @device_cache[cnx] ||= {}
341
+ @device_cache[cnx][instance_id] = nil if refresh
342
+ return @device_cache[cnx][instance_id] if @device_cache[cnx][instance_id]
343
+
257
344
  data = cnx.get("#{RSRC_VERSION}/#{RSRC_PATH}/#{instance_id}/#{DEVICES_RSRC}")[:results]
258
345
 
259
- data.map { |dev| Jamf::DeviceEnrollmentDevice.new dev }
346
+ data.map! { |dev| Jamf::DeviceEnrollmentDevice.new dev }
347
+ @device_cache[cnx][instance_id] = data
260
348
  end
261
349
  private_class_method :devices_for_instance_id
262
350
 
@@ -287,6 +375,10 @@ module Jamf
287
375
  sync_history :latest
288
376
  end
289
377
 
378
+ def disown(*sns)
379
+ self.class.disown sns, from_instance: @id, cnx: @cnx
380
+ end
381
+
290
382
  end # class
291
383
 
292
384
  end # module
@@ -0,0 +1,119 @@
1
+ # Copyright 2019 Pixar
2
+
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ # with the following modification; you may not use this file except in
6
+ # compliance with the Apache License and the following modification to it:
7
+ # Section 6. Trademarks. is deleted and replaced with:
8
+ #
9
+ # 6. Trademarks. This License does not grant permission to use the trade
10
+ # names, trademarks, service marks, or product names of the Licensor
11
+ # and its affiliates, except as required to comply with Section 4(c) of
12
+ # the License and to reproduce the content of the NOTICE file.
13
+ #
14
+ # You may obtain a copy of the Apache License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the Apache License with the above modification is
20
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ # KIND, either express or implied. See the Apache License for the specific
22
+ # language governing permissions and limitations under the Apache License.
23
+ #
24
+ #
25
+
26
+ # The Module
27
+ module Jamf
28
+
29
+ # Classes
30
+ #####################################
31
+
32
+ # A building defined in the JSS
33
+ class TimeZone < Jamf::CollectionResource
34
+
35
+ # Mix-Ins
36
+ #####################################
37
+
38
+ extend Jamf::Immutable
39
+ extend Jamf::UnCreatable
40
+ extend Jamf::UnDeletable
41
+
42
+ # Constants
43
+ #####################################
44
+
45
+ RSRC_VERSION = 'v1'.freeze
46
+
47
+ RSRC_PATH = 'time-zones'.freeze
48
+
49
+ # Object Model / Attributes
50
+ # See APIObject class documentation for details
51
+ # of how the OBJECT_MODEL hash works.
52
+ #####################################
53
+ OBJECT_MODEL = {
54
+
55
+ # @!attribute [r] zoneId
56
+ # @return [String]
57
+ zoneId: {
58
+ class: :string,
59
+ identifier: :primary,
60
+ aliases: [:id]
61
+ },
62
+
63
+ # @!attribute displayName
64
+ # @return [String]
65
+ displayName: {
66
+ class: :string,
67
+ identifier: true,
68
+ aliases: [:name]
69
+ },
70
+
71
+ # @!attribute [r] region
72
+ # @return [String]
73
+ region: {
74
+ class: :string
75
+ }
76
+ }.freeze
77
+ parse_object_model
78
+
79
+ # The offset from UTC, as a string.
80
+ #
81
+ # This is as it would appear at the end of an ISO8601 formatted time,
82
+ # e.g. -0945 or +1200
83
+ #
84
+ # Note that ISO8601 accepts the formats: +/-hh:mm, +/-hhmm, or +/-hh
85
+ #
86
+ # @return [Integer] The offset from UTC, as a string
87
+ #
88
+ def utc_offset_str
89
+ return @utc_offset_str if @utc_offset_str
90
+
91
+ displayName =~ /\(([+-]\d{4})\)/
92
+ @utc_offset_str = Regexp.last_match[1]
93
+ end
94
+
95
+ # @return [Integer] The offset from UTC, in seconds
96
+ #
97
+ def utc_offset
98
+ return @utc_offset if @utc_offset
99
+
100
+ sign = utc_offset_str[0]
101
+ secs = utc_offset_str[1..2].to_i * 3600
102
+ secs += utc_offset_str[3..4].to_i * 60
103
+ # negate if needed
104
+ @utc_offset = sign == '+' ? secs : -secs
105
+ end
106
+
107
+ # Give a Time object, whats the matching local time in this TimeZone?
108
+ #
109
+ # @param othertime[Time] a Time or Jamf::Timestamp object
110
+ #
111
+ # @return [Time] othertime, in the local time in this time zone
112
+ #
113
+ def localtime(othertime)
114
+ othertime.getlocal utc_offset
115
+ end
116
+
117
+ end # class
118
+
119
+ end # module
@@ -24,7 +24,7 @@
24
24
  ###
25
25
 
26
26
  ###
27
- module JSS
27
+ module Jamf
28
28
 
29
29
  # This class represents a Jamf/JSS Client computer, on which
30
30
  # this code is running.
@@ -78,10 +78,17 @@ module JSS
78
78
  # the path to a users byhost folder from home
79
79
  USER_PREFS_BYHOST_FOLDER = 'Library/Preferences/ByHost/'
80
80
 
81
- # Class Methods
82
- #####################################
81
+ # If some processs C has a parent process P whose command (via ps -o comm)
82
+ # matches this, then process C is being run by Jamf
83
+ POLICY_SCRIPT_CMD_RE = %r{sh -c PATH=\$PATH:/usr/local/jamf/bin; '/Library/Application Support/JAMF/tmp/.* >& '/Library/Application Support/JAMF/tmp/\d+.tmp}.freeze
83
84
 
85
+ # If some processs C has a parent process P whose command (via ps -o comm)
86
+ # matching POLICY_SCRIPT_CMD_RE, and process P has a parent process G that
87
+ # matches this (C is grandchild of G), then process C is being run by a Jamf policy
88
+ POLICY_CMD_RE = %r{/bin/jamf policy }.freeze
84
89
 
90
+ # Class Methods
91
+ #####################################
85
92
 
86
93
  # Get the current IP address as a String.
87
94
  #
@@ -292,6 +299,54 @@ module JSS
292
299
  dir ? Pathname.new(dir) : nil
293
300
  end
294
301
 
302
+ # Search up the process lineage to see if any ancestor processes indicate
303
+ # that the current process is being run by a Jamf Policy.
304
+ #
305
+ # @return [Boolean] Is the current process being run as a script by a Jamf Policy?
306
+ #
307
+ def self.script_running_via_policy?
308
+ root_ps_lines = `ps -u root -x -o pid -o ppid -o user -o command`.lines
309
+
310
+ parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd Process.pid, root_ps_lines
311
+ return false unless parent_pid
312
+
313
+ until parent_command =~ POLICY_SCRIPT_CMD_RE || parent_pid.nil?
314
+ parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd parent_pid, root_ps_lines
315
+ return false if parent_pid.zero?
316
+ end
317
+ return false if parent_pid.nil?
318
+
319
+ # if we're here, our parent is a jamf process, lets confirm that its
320
+ # a jamf policy
321
+ until parent_command =~ POLICY_CMD_RE || parent_pid.nil?
322
+ parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd parent_pid, root_ps_lines
323
+ return false if parent_pid.zero?
324
+ end
325
+ !parent_pid.nil?
326
+ end
327
+
328
+ # given a pid and optionally the output of `ps -o pid -o ppid -o user -o command`
329
+ # return an array with the pid, user, and command of the pid's parent process
330
+ #
331
+ # @param pid [Integer, String] the process id for which we want parent info
332
+ #
333
+ # @param ps_lines [Array<String>] the lines of output from
334
+ # `ps -o pid -o ppid -o user -o command` possibly with other options.
335
+ # If omitted, `ps -a -x -o pid -o ppid -o user -o command` will be used
336
+ #
337
+ # @return [Array<Integer, String, String>] the pid, user, and commandline
338
+ # of the parent process of the given pid. All will be nil if not found
339
+ #
340
+ def self.parent_pid_user_and_cmd(pid, ps_lines = nil)
341
+ ps_lines ||= `ps -a -x -o pid -o ppid -o user -o command`.lines
342
+
343
+ parent_ps_line = ps_lines.select { |l| l =~ /^\s*#{pid}\s/ }.first
344
+ return [nil, nil, nil] unless parent_ps_line
345
+
346
+ parent_ps_line =~ /^\s*\d+\s+(\d+)\s+(\S+)\s+(.*)$/
347
+ [Regexp.last_match(1).to_i, Regexp.last_match(2), Regexp.last_match(3)]
348
+ end
349
+
295
350
  end # class Client
296
351
 
297
352
  end # module
@@ -159,7 +159,7 @@ module Jamf
159
159
  # @return [void]
160
160
  #
161
161
  def read_global
162
- read GLOBAL_CONF_FILE if GLOBAL_CONF_FILE.file? && GLOBAL_CONF_FILE.readable?
162
+ read GLOBAL_CONF_FILE if GLOBAL_CONF_FILE&.file? && GLOBAL_CONF_FILE.readable?
163
163
  end
164
164
 
165
165
  # (Re)read the user prefs, if it exists.
@@ -167,7 +167,7 @@ module Jamf
167
167
  # @return [void]
168
168
  #
169
169
  def read_user
170
- read USER_CONF_FILE if USER_CONF_FILE.file? && USER_CONF_FILE.readable?
170
+ read USER_CONF_FILE if USER_CONF_FILE&.file? && USER_CONF_FILE.readable?
171
171
  end
172
172
 
173
173
  # Clear the settings and reload the prefs files, or another file if provided
@@ -40,7 +40,8 @@ module JamfRubyExtensions
40
40
  # nil if it doesn't exist
41
41
  #
42
42
  def j_ci_fetch(somestring)
43
- each { |s| return s if s.is_a?(String) && s.casecmp?(somestring) }
43
+ # select { |s| s&.casecmp? somestring }.first
44
+ each { |s| return s if s&.casecmp?(somestring) }
44
45
  nil
45
46
  end
46
47
 
@@ -51,7 +52,7 @@ module JamfRubyExtensions
51
52
  # @return [Boolean]
52
53
  #
53
54
  def j_ci_include?(somestring)
54
- any? { |s| s.casecmp? somestring if s.is_a? String }
55
+ any? { |s| s&.casecmp? somestring }
55
56
  end
56
57
 
57
58
  end # module
@@ -27,6 +27,6 @@
27
27
  module JSS
28
28
 
29
29
  ### The version of ruby-jss
30
- VERSION = '1.2.4a3'.freeze
30
+ VERSION = '1.2.4a4'.freeze
31
31
 
32
32
  end # module
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-jss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4a3
4
+ version: 1.2.4a4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Lasell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-12-06 00:00:00.000000000 Z
12
+ date: 2020-01-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: plist
@@ -218,6 +218,7 @@ files:
218
218
  - lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb
219
219
  - lib/jamf/api/resources/collection_resources/script.rb
220
220
  - lib/jamf/api/resources/collection_resources/site.rb
221
+ - lib/jamf/api/resources/collection_resources/time_zone.rb
221
222
  - lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb
222
223
  - lib/jamf/api/resources/singleton_resources/authorization.rb
223
224
  - lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb