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