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 +4 -4
- data/README.md +1 -1
- data/lib/jamf.rb +1 -0
- data/lib/jamf/api/abstract_classes/collection_resource.rb +23 -12
- data/lib/jamf/api/abstract_classes/json_object.rb +1 -5
- data/lib/jamf/api/abstract_classes/prestage.rb +109 -33
- data/lib/jamf/api/connection.rb +73 -22
- data/lib/jamf/api/connection/token.rb +9 -5
- data/lib/jamf/api/json_objects/device_enrollment_device.rb +5 -3
- data/lib/jamf/api/json_objects/prestage_assignment.rb +4 -2
- data/lib/jamf/api/mixins/change_log.rb +2 -0
- data/lib/jamf/api/mixins/searchable.rb +2 -2
- data/lib/jamf/api/resources/collection_resources/department.rb +1 -0
- data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +109 -17
- data/lib/jamf/api/resources/collection_resources/time_zone.rb +119 -0
- data/lib/jamf/client.rb +58 -3
- data/lib/jamf/configuration.rb +2 -2
- data/lib/jamf/ruby_extensions/array/utils.rb +3 -2
- data/lib/jss/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 745114754cc7073bbf494e8eb54f48962c9cafff99da985108c90b5bc7ccc62c
|
4
|
+
data.tar.gz: 6455a7c53abc89c47a03b1bd9538bc0bd3375eab1776ca9c5a15425595ed2ed3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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::
|
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
|
|
data/lib/jamf.rb
CHANGED
@@ -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
|
-
|
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?
|
156
|
+
identifiers.include? real_ident
|
151
157
|
|
152
|
-
|
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
|
167
|
-
#
|
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
|
170
|
-
# then you can specify the identifier
|
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
|
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 :
|
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.
|
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 :
|
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.
|
248
|
-
#
|
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.
|
267
|
-
#
|
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
|
-
|
287
|
-
raise Jamf::NoSuchItemError, "No #{self} matching '#{prestage_ident}'" unless
|
286
|
+
psid = valid_id prestage_ident, cnx: cnx
|
287
|
+
raise Jamf::NoSuchItemError, "No #{self} matching '#{prestage_ident}'" unless psid
|
288
288
|
|
289
|
-
return
|
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
|
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
|
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
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
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
|
|
data/lib/jamf/api/connection.rb
CHANGED
@@ -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
|
81
|
-
DFT_TOKEN_REFRESH =
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
706
|
-
|
707
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
|
@@ -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
|
@@ -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
|
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 .
|
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
|
data/lib/jamf/client.rb
CHANGED
@@ -24,7 +24,7 @@
|
|
24
24
|
###
|
25
25
|
|
26
26
|
###
|
27
|
-
module
|
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
|
-
#
|
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
|
data/lib/jamf/configuration.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
55
|
+
any? { |s| s&.casecmp? somestring }
|
55
56
|
end
|
56
57
|
|
57
58
|
end # module
|
data/lib/jss/version.rb
CHANGED
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.
|
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:
|
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
|