ruby-jss 1.2.4a3 → 1.2.4a4

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.

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