ruby-jss 1.3.2 → 1.5.3

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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +122 -0
  3. data/lib/jamf.rb +18 -16
  4. data/lib/jamf/api/base_classes/collection_resource.rb +613 -0
  5. data/lib/jamf/api/{abstract_classes → base_classes}/json_object.rb +109 -101
  6. data/lib/jamf/api/{abstract_classes → base_classes}/prestage.rb +55 -30
  7. data/lib/jamf/api/{abstract_classes → base_classes}/resource.rb +10 -6
  8. data/lib/jamf/api/{abstract_classes → base_classes}/singleton_resource.rb +4 -3
  9. data/lib/jamf/api/connection.rb +13 -9
  10. data/lib/jamf/api/connection/api_error.rb +8 -8
  11. data/lib/jamf/api/connection/token.rb +36 -15
  12. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  13. data/lib/jamf/api/json_objects/{location.rb → device_enrollment_device_sync_state.rb} +27 -41
  14. data/lib/jamf/api/json_objects/device_enrollment_sync_status.rb +1 -1
  15. data/lib/jamf/api/json_objects/{attachment.rb → locale.rb} +14 -23
  16. data/lib/jamf/api/json_objects/md_prestage_name.rb +1 -1
  17. data/lib/jamf/api/json_objects/md_prestage_names.rb +2 -2
  18. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  19. data/lib/jamf/api/json_objects/prestage_assignment.rb +2 -2
  20. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  21. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  22. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  23. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  24. data/lib/jamf/api/mixins/{abstract.rb → base_class.rb} +34 -16
  25. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  26. data/lib/jamf/api/mixins/change_log.rb +201 -51
  27. data/lib/jamf/api/{resources/collection_resources/computer.rb → mixins/filterable.rb} +19 -17
  28. data/lib/jamf/api/mixins/pageable.rb +208 -0
  29. data/lib/jamf/api/{json_objects/installed_application.rb → mixins/sortable.rb} +33 -33
  30. data/lib/jamf/api/resources/collection_resources/building.rb +16 -9
  31. data/lib/jamf/api/resources/collection_resources/category.rb +5 -4
  32. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +12 -5
  33. data/lib/jamf/api/resources/collection_resources/department.rb +1 -3
  34. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  35. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  36. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +25 -23
  37. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  38. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  39. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  40. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  41. data/lib/jamf/validate.rb +63 -24
  42. data/lib/jamf/version.rb +1 -1
  43. data/lib/jss.rb +2 -1
  44. data/lib/jss/api_connection.rb +113 -406
  45. data/lib/jss/api_object.rb +10 -20
  46. data/lib/jss/api_object/advanced_search.rb +27 -26
  47. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  48. data/lib/jss/api_object/categorizable.rb +1 -1
  49. data/lib/jss/api_object/computer.rb +13 -0
  50. data/lib/jss/api_object/configuration_profile.rb +60 -4
  51. data/lib/jss/api_object/directory_binding_type.rb +66 -60
  52. data/lib/jss/api_object/directory_binding_type/active_directory.rb +71 -34
  53. data/lib/jss/api_object/directory_binding_type/admitmac.rb +536 -467
  54. data/lib/jss/api_object/directory_binding_type/centrify.rb +21 -7
  55. data/lib/jss/api_object/directory_binding_type/open_directory.rb +4 -4
  56. data/lib/jss/api_object/distribution_point.rb +2 -2
  57. data/lib/jss/api_object/dock_item.rb +102 -96
  58. data/lib/jss/api_object/ebook.rb +1 -2
  59. data/lib/jss/api_object/extendable.rb +1 -1
  60. data/lib/jss/api_object/extension_attribute.rb +4 -3
  61. data/lib/jss/api_object/group.rb +33 -2
  62. data/lib/jss/api_object/mac_application.rb +107 -8
  63. data/lib/jss/api_object/network_segment.rb +43 -12
  64. data/lib/jss/api_object/package.rb +1 -1
  65. data/lib/jss/api_object/patch_source.rb +10 -9
  66. data/lib/jss/api_object/policy.rb +217 -28
  67. data/lib/jss/api_object/printer.rb +10 -4
  68. data/lib/jss/api_object/scopable.rb +10 -15
  69. data/lib/jss/api_object/scopable/scope.rb +389 -73
  70. data/lib/jss/api_object/self_servable.rb +17 -9
  71. data/lib/jss/api_object/uploadable.rb +1 -1
  72. data/lib/jss/api_object/user.rb +42 -1
  73. data/lib/jss/api_object/vpp_account.rb +209 -0
  74. data/lib/jss/api_object/vppable.rb +169 -13
  75. data/lib/jss/exceptions.rb +3 -0
  76. data/lib/jss/server.rb +15 -0
  77. data/lib/jss/utility.rb +142 -37
  78. data/lib/jss/validate.rb +53 -10
  79. data/lib/jss/version.rb +1 -1
  80. metadata +45 -61
  81. data/lib/jamf/api/abstract_classes/advanced_search.rb +0 -86
  82. data/lib/jamf/api/abstract_classes/collection_resource.rb +0 -433
  83. data/lib/jamf/api/abstract_classes/generic_reference.rb +0 -145
  84. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +0 -126
  85. data/lib/jamf/api/json_objects/account_prefs.rb +0 -79
  86. data/lib/jamf/api/json_objects/android_details.rb +0 -139
  87. data/lib/jamf/api/json_objects/appletv_details.rb +0 -110
  88. data/lib/jamf/api/json_objects/cellular_network.rb +0 -151
  89. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +0 -67
  90. data/lib/jamf/api/json_objects/criterion.rb +0 -152
  91. data/lib/jamf/api/json_objects/extension_attribute_value.rb +0 -128
  92. data/lib/jamf/api/json_objects/installed_certificate.rb +0 -53
  93. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +0 -67
  94. data/lib/jamf/api/json_objects/installed_ebook.rb +0 -58
  95. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +0 -59
  96. data/lib/jamf/api/json_objects/ios_details.rb +0 -244
  97. data/lib/jamf/api/json_objects/mobile_device_details.rb +0 -219
  98. data/lib/jamf/api/json_objects/mobile_device_security.rb +0 -101
  99. data/lib/jamf/api/json_objects/purchasing_data.rb +0 -125
  100. data/lib/jamf/api/mixins/locatable.rb +0 -124
  101. data/lib/jamf/api/mixins/referable.rb +0 -92
  102. data/lib/jamf/api/resources/collection_resources/account.rb +0 -163
  103. data/lib/jamf/api/resources/collection_resources/advanced_mobile_device_search.rb +0 -52
  104. data/lib/jamf/api/resources/collection_resources/advanced_user_search.rb +0 -52
  105. data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +0 -45
  106. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +0 -315
  107. data/lib/jamf/api/resources/collection_resources/site.rb +0 -77
  108. data/lib/jamf/api/resources/singleton_resources/authorization.rb +0 -88
  109. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +0 -139
  110. data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +0 -95
@@ -376,9 +376,9 @@ module JSS
376
376
  #
377
377
  # @author Tyler Morgan
378
378
  #
379
- # @param newvalue [String]
379
+ # @param newvalue [String, Float, Array[String], Array[Float]]
380
380
  #
381
- # @raise [JSS::InvalidDataError] If newvalue is not a String
381
+ # @raise [JSS::InvalidDataError] If newvalue is not a String, Float, Array containing Strings, or Array containing Floats.
382
382
  #
383
383
  # @example Limit Printer object to only High Sierra devices and Mojave 10.14.5 OS versions
384
384
  # printer.os_requirements = "10.13.x, 10.14.5"
@@ -386,8 +386,14 @@ module JSS
386
386
  # @return [void]
387
387
  def os_requirements=(newvalue)
388
388
 
389
- raise JSS::InvalidDataError, "os_requirements must be a string." unless newvalue.is_a? String
390
-
389
+ if newvalue.is_a? Array
390
+ # Parse Array
391
+ raise JSS::InvalidDataError, "If setting os_requirements with an array, it must contain strings or floats." unless newvalue[0].is_a?(String) || newvalue[0].is_a?(Float)
392
+ newvalue = newvalue.map { |x| x.to_s }.join(',')
393
+ else
394
+ raise JSS::InvalidDataError, "os_requirements must either be a string, float, or an array containing strings or floats." unless (newvalue.is_a?(String) || newvalue.is_a?(Float)) && !newvalue.nil?
395
+ end
396
+
391
397
  @os_requirements = newvalue
392
398
 
393
399
  @need_to_update = true
@@ -100,22 +100,17 @@ module JSS
100
100
  @need_to_update = true if @in_jss
101
101
  end
102
102
 
103
-
104
- ### A wrapper around the update method, to try catching RestClient::Conflict
105
- ### 409 errors when we couldn't verify all ldap users/groups due to lack of ldap connections
106
- ###
103
+ # A wrapper around the update method, to try catching 409 conflict errors
104
+ # when we couldn't verify all ldap users/groups due to lack of ldap connections
105
+ #
107
106
  def update
108
- begin
109
- super
110
-
111
- rescue RestClient::Conflict => conflict
112
- if self.scope.unable_to_verify_ldap_entries == true
113
- raise JSS::InvalidDataError, "Potentially non-existant LDAP user or group in new scope values."
114
- else
115
- raise conflict
116
- end
117
-
118
- end # begin
107
+ super
108
+ rescue JSS::ConflictError => conflict
109
+ if scope.unable_to_verify_ldap_entries == true
110
+ raise JSS::InvalidDataError, "Potentially non-existant LDAP user or group in new scope values."
111
+ else
112
+ raise conflict
113
+ end
119
114
  end # update
120
115
 
121
116
  end # module Scopable
@@ -41,17 +41,21 @@ module JSS
41
41
  # This class provides methods for adding, removing, or fully replacing the
42
42
  # various items in scope's realms: targets, limitations, and exclusions.
43
43
  #
44
- # IMPORTANT:
44
+ # This class also provides a way to see if a machine will be included in
45
+ # this scope.
46
+ #
47
+ # IMPORTANT - Users & User Groups in Targets and Exclusions:
48
+ #
45
49
  # The classic API has bugs regarding the use of Users, UserGroups,
46
50
  # LDAP/Local Users, & LDAP User Groups in scopes. Here's a discussion
47
51
  # of those bugs and how ruby-jss handles them.
48
52
  #
49
53
  # Targets/Inclusions
50
- # - 'Users' can only be JSS::Users - No LDAP
54
+ # - 'Users' in the Scope UI can only be JSS::Users - No LDAP
51
55
  # - BUG: They do not appear in API data (XML or JSON) and are
52
56
  # NOT SUPPORTED in ruby-jss.
53
57
  # - You must use the Web UI to work with them in a Scope.
54
- # - 'User Groups' can only be JSS::UserGroups - No LDAP
58
+ # - 'User Groups' in the Scope UI can only be JSS::UserGroups - No LDAP
55
59
  # - BUG: They do not appear in API data (XML or JSON) and are
56
60
  # NOT SUPPORTED in ruby-jss.
57
61
  # - You must use the Web UI to work with them in a Scope.
@@ -70,11 +74,11 @@ module JSS
70
74
  # scope=>limitations=>user_groups
71
75
  #
72
76
  # Exclusions, combines the behavior of Inclusions & Limitations
73
- # - 'Users' can only be JSS::Users - No LDAP
77
+ # - 'Users' in the Scope UI can only be JSS::Users - No LDAP
74
78
  # - BUG: They do not appear in API data (XML or JSON) and are
75
79
  # NOT SUPPORTED in ruby-jss.
76
80
  # - You must use the Web UI to work with them in a Scope.
77
- # - 'User Groups' can only be JSS::UserGroups - No LDAP
81
+ # - 'User Groups' in the Scope UI can only be JSS::UserGroups - No LDAP
78
82
  # - BUG: They do not appear in API data (XML or JSON) and are
79
83
  # NOT SUPPORTED in ruby-jss.
80
84
  # - You must use the Web UI to work with them in a Scope.
@@ -220,62 +224,70 @@ module JSS
220
224
  # A reference to the object that contains this Scope
221
225
  #
222
226
  # For telling it when a change is made and an update needed
227
+ # and for accessing its api connection
223
228
  attr_accessor :container
224
229
 
225
230
  # @return [Boolean] should we expect a potential 409 Conflict
226
231
  # if we can't connect to LDAP servers for verification?
227
232
  attr_accessor :unable_to_verify_ldap_entries
228
233
 
229
- # what type of target is this scope for? Computers or Mobiledevices?
234
+ # what type of target is this scope for? Computers or MobileDevices?
230
235
  attr_reader :target_class
231
236
 
232
- # @return [Hash<Array>]
233
- #
234
- # The items which form the base scope of included targets
235
- #
236
- # This is the group of targets to which the limitations and exclusions apply.
237
- # they keys are:
238
- # - :targets
239
- # - :target_groups
240
- # - :departments
241
- # - :buildings
242
- # and the values are Arrays of names of those things.
243
- #
244
- attr_reader :inclusions
237
+ # what type of target group is this scope for? ComputerGroups or MobileDeviceGroups?
238
+ attr_reader :group_class
245
239
 
246
240
  # @return [Boolean]
247
241
  #
248
242
  # Does this scope cover all targets?
249
243
  #
250
- # If this is true, the @inclusions Hash is ignored, and all
244
+ # If this is true, the @targets Hash is ignored, and all
251
245
  # targets in the JSS form the base scope.
252
246
  #
253
247
  attr_reader :all_targets
248
+ alias all_targets? all_targets
254
249
 
255
- # @return [Hash<Array>]
250
+ # The items which form the base scope of included targets
256
251
  #
257
- # The items in these arrays are the limitations applied to targets in the @inclusions .
252
+ # This is the group of targets to which the limitations and exclusions apply.
253
+ # they keys are:
254
+ # - :computers or :mobile_devices (which are directly targeted)
255
+ # - :direct_targets - a synonym for :mobile_devices or :computers
256
+ # - :computer_groups or :mobile_device_groups (which target all of their memebers)
257
+ # - :group_targets - a synonym for :computer_groups or :mobile_device_groups
258
+ # - :departments
259
+ # - :buildings
260
+ # and the values are Arrays of names of those things.
258
261
  #
259
- # The arrays of names are:
262
+ # @return [Hash{Symbol: Array<Integer>}]
263
+ attr_reader :targets
264
+ # backward compatibility
265
+ alias inclusions targets
266
+
267
+ # The items in these arrays are the limitations applied to targets in the @targets .
268
+ #
269
+ # The arrays of ids are:
260
270
  # - :network_segments
261
- # - :users
271
+ # - :jamf_ldap_users
262
272
  # - :user_groups
263
273
  #
274
+ # @return [Hash{Symbol: Array<Integer, String>}]
264
275
  attr_reader :limitations
265
276
 
266
- # @return [Hash<Array>]
277
+ # The items in these arrays are the exclusions applied to targets in the @targets .
267
278
  #
268
- # The items in these arrays are the exclusions applied to targets in the @inclusions .
269
- #
270
- # The arrays of names are:
271
- # - :targets
272
- # - :target_groups
279
+ # The arrays of ids are:
280
+ # - :computers or :mobile_devices (which are directly excluded)
281
+ # - :direct_exclusions - a synonym for :mobile_devices or :computers
282
+ # - :computer_groups or :mobile_device_groups (which exclude all of their memebers)
283
+ # - :group_exclusions - a synonym for :computer_groups or :mobile_device_groups
273
284
  # - :departments
274
285
  # - :buildings
275
286
  # - :network_segments
276
287
  # - :users
277
288
  # - :user_groups
278
289
  #
290
+ # @return [Hash{Symbol: Array<Integer, String>}]
279
291
  attr_reader :exclusions
280
292
 
281
293
  # Public Instance Methods
@@ -299,7 +311,7 @@ module JSS
299
311
  @group_key = TARGETS_AND_GROUPS[@target_key]
300
312
  @group_class = SCOPING_CLASSES[@group_key]
301
313
 
302
- @inclusion_keys = [@target_key, @group_key] + INCLUSIONS
314
+ @target_keys = [@target_key, @group_key] + INCLUSIONS
303
315
  @exclusion_keys = [@target_key, @group_key] + EXCLUSIONS
304
316
 
305
317
  @all_key = "all_#{target_key}".to_sym
@@ -307,11 +319,13 @@ module JSS
307
319
 
308
320
  # Everything gets mapped from an Array of Hashes to
309
321
  # an Array of ids
310
- @inclusions = {}
311
- @inclusion_keys.each do |k|
322
+ @targets = {}
323
+ @target_keys.each do |k|
312
324
  raw_scope[k] ||= []
313
- @inclusions[k] = raw_scope[k].compact.map { |n| n[:id].to_i }
314
- end # @inclusion_keys.each do |k|
325
+ @targets[k] = raw_scope[k].compact.map { |n| n[:id].to_i }
326
+ @targets[:direct_targets] = @targets[k] if k == @target_key
327
+ @targets[:group_targets] = @targets[k] if k == @group_key
328
+ end # @target_keys.each do |k|
315
329
 
316
330
  # the :users key from the API is what we call :jamf_ldap_users
317
331
  # and the :user_groups key from the API we call :ldap_user_groups
@@ -365,6 +379,8 @@ module JSS
365
379
  api_data = raw_scope[:exclusions][k]
366
380
  api_data ||= []
367
381
  @exclusions[k] = api_data.compact.map { |n| n[:id].to_i }
382
+ @exclusions[:direct_exclusions] = @exclusions[k] if k == @target_key
383
+ @exclusions[:group_exclusions] = @exclusions[k] if k == @group_key
368
384
  end # if ...elsif... else
369
385
  end # @exclusion_keys.each
370
386
  end # if raw_scope[:exclusions]
@@ -382,8 +398,8 @@ module JSS
382
398
  # @return [void]
383
399
  #
384
400
  def include_all(clear = false)
385
- @inclusions = {}
386
- @inclusion_keys.each { |k| @inclusions[k] = [] }
401
+ @targets = {}
402
+ @target_keys.each { |k| @targets[k] = [] }
387
403
  @all_targets = true
388
404
  if clear
389
405
  @limitations = {}
@@ -392,7 +408,7 @@ module JSS
392
408
  @exclusions = {}
393
409
  @exclusion_keys.each { |k| @exclusions[k] = [] }
394
410
  end
395
- @container.should_update if @container
411
+ @container&.should_update
396
412
  end
397
413
 
398
414
  # Replace a list of item names for as targets in this scope.
@@ -420,7 +436,7 @@ module JSS
420
436
  list.map! do |ident|
421
437
  item_id = validate_item(:target, key, ident)
422
438
 
423
- if @exclusions[key] && @exclusions[key].include?(item_id)
439
+ if @exclusions[key]&.include?(item_id)
424
440
  raise JSS::AlreadyExistsError, \
425
441
  "Can't set #{key} target to '#{ident}' because it's already an explicit exclusion."
426
442
  end
@@ -428,11 +444,11 @@ module JSS
428
444
  item_id
429
445
  end # each
430
446
 
431
- return nil if list.sort == @inclusions[key].sort
447
+ return nil if list.sort == @targets[key].sort
432
448
 
433
- @inclusions[key] = list
449
+ @targets[key] = list
434
450
  @all_targets = false
435
- @container.should_update if @container
451
+ @container&.should_update
436
452
  end # sinclude_in_scope
437
453
  alias set_target set_targets
438
454
  alias set_inclusion set_targets
@@ -458,13 +474,13 @@ module JSS
458
474
  def add_target(key, item)
459
475
  key = pluralize_key(key)
460
476
  item_id = validate_item(:target, key, item)
461
- return if @inclusions[key] && @exclusions[key].include?(item_id)
477
+ return if @targets[key]&.include?(item_id)
462
478
 
463
- raise JSS::AlreadyExistsError, "Can't set #{key} target to '#{item}' because it's already an explicit exclusion." if @exclusions[key] && @exclusions[key].include?(item_id)
479
+ raise JSS::AlreadyExistsError, "Can't set #{key} target to '#{item}' because it's already an explicit exclusion." if @exclusions[key]&.include?(item_id)
464
480
 
465
- @inclusions[key] << item_id
481
+ @targets[key] << item_id
466
482
  @all_targets = false
467
- @container.should_update if @container
483
+ @container&.should_update
468
484
  end
469
485
  alias add_inclusion add_target
470
486
 
@@ -483,10 +499,10 @@ module JSS
483
499
  key = pluralize_key(key)
484
500
  item_id = validate_item :target, key, item, error_if_not_found: false
485
501
  return unless item_id
486
- return unless @inclusions[key] && @exclusions[key].include?(item_id)
502
+ return unless @targets[key]&.include?(item_id)
487
503
 
488
- @inclusions[key].delete item_id
489
- @container.should_update if @container
504
+ @targets[key].delete item_id
505
+ @container&.should_update
490
506
  end
491
507
  alias remove_inclusion remove_target
492
508
 
@@ -513,7 +529,7 @@ module JSS
513
529
  # check the idents
514
530
  list.map! do |ident|
515
531
  item_id = validate_item(:limitation, key, ident)
516
- if @exclusions[key] && @exclusions[key].include?(item_id)
532
+ if @exclusions[key]&.include?(item_id)
517
533
  raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
518
534
  end
519
535
 
@@ -523,7 +539,7 @@ module JSS
523
539
  return nil if list.sort == @limitations[key].sort
524
540
 
525
541
  @limitations[key] = list
526
- @container.should_update if @container
542
+ @container&.should_update
527
543
  end # set_limitation
528
544
  alias set_limitations set_limitation
529
545
 
@@ -545,14 +561,14 @@ module JSS
545
561
  def add_limitation(key, item)
546
562
  key = pluralize_key(key)
547
563
  item_id = validate_item(:limitation, key, item)
548
- return nil if @limitations[key] && @exclusions[key].include?(item_id)
564
+ return nil if @limitations[key]&.include?(item_id)
549
565
 
550
- if @exclusions[key] && @exclusions[key].include?(item_id)
566
+ if @exclusions[key]&.include?(item_id)
551
567
  raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
552
568
  end
553
569
 
554
570
  @limitations[key] << item_id
555
- @container.should_update if @container
571
+ @container&.should_update
556
572
  end
557
573
 
558
574
  # Remove a single item for limiting this scope.
@@ -572,10 +588,10 @@ module JSS
572
588
  key = pluralize_key(key)
573
589
  item_id = validate_item :limitation, key, item, error_if_not_found: false
574
590
  return unless item_id
575
- return unless @limitations[key] && @exclusions[key].include?(item_id)
591
+ return unless @limitations[key]&.include?(item_id)
576
592
 
577
593
  @limitations[key].delete item_id
578
- @container.should_update if @container
594
+ @container&.should_update
579
595
  end ###
580
596
 
581
597
  # Replace an exclusion list for this scope
@@ -600,8 +616,10 @@ module JSS
600
616
  list.map! do |ident|
601
617
  item_id = validate_item(:exclusion, key, ident)
602
618
  case key
603
- when *@inclusion_keys
604
- raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @inclusions[key] && @exclusions[key].include?(item_id)
619
+ when *@target_keys
620
+ if @targets[key] && @exclusions[key].include?(item_id)
621
+ raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included."
622
+ end
605
623
  when *LIMITATIONS
606
624
  if @limitations[key] && @exclusions[key].include?(item_id)
607
625
  raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already an explicit limitation."
@@ -613,7 +631,7 @@ module JSS
613
631
  return nil if list.sort == @exclusions[key].sort
614
632
 
615
633
  @exclusions[key] = list
616
- @container.should_update if @container
634
+ @container&.should_update
617
635
  end # limit scope
618
636
 
619
637
  # Add a single item for exclusions of this scope.
@@ -632,14 +650,14 @@ module JSS
632
650
  def add_exclusion(key, item)
633
651
  key = pluralize_key(key)
634
652
  item_id = validate_item(:exclusion, key, item)
635
- return if @exclusions[key] && @exclusions[key].include?(item_id)
653
+ return if @exclusions[key]&.include?(item_id)
636
654
 
637
- raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @inclusions[key] && @inclusions[key].include?(item)
655
+ raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @targets[key]&.include?(item)
638
656
 
639
- raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key] && @limitations[key].include?(item)
657
+ raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key]&.include?(item)
640
658
 
641
659
  @exclusions[key] << item_id
642
- @container.should_update if @container
660
+ @container&.should_update
643
661
  end
644
662
 
645
663
  # Remove a single item for exclusions of this scope
@@ -656,10 +674,10 @@ module JSS
656
674
  def remove_exclusion(key, item)
657
675
  key = pluralize_key(key)
658
676
  item_id = validate_item :exclusion, key, item, error_if_not_found: false
659
- return unless @exclusions[key] && @exclusions[key].include?(item_id)
677
+ return unless @exclusions[key]&.include?(item_id)
660
678
 
661
679
  @exclusions[key].delete item_id
662
- @container.should_update if @container
680
+ @container&.should_update
663
681
  end
664
682
 
665
683
  # @api private
@@ -672,7 +690,8 @@ module JSS
672
690
  scope = REXML::Element.new 'scope'
673
691
  scope.add_element(@all_key.to_s).text = @all_targets
674
692
 
675
- @inclusions.each do |klass, list|
693
+ @target_keys.each do |klass|
694
+ list = @targets[klass]
676
695
  list.compact!
677
696
  list.delete 0
678
697
  list_as_hashes = list.map { |i| { id: i } }
@@ -702,7 +721,8 @@ module JSS
702
721
  end
703
722
 
704
723
  exclusions = scope.add_element('exclusions')
705
- @exclusions.each do |klass, list|
724
+ @exclusion_keys.each do |klass|
725
+ list = @exclusions[klass]
706
726
  list.compact!
707
727
  list.delete 0
708
728
  if klass == :jamf_ldap_users
@@ -737,9 +757,59 @@ module JSS
737
757
  vars
738
758
  end
739
759
 
740
- # Aliases
760
+ # Return a hash of id => name for all machines in the target class
761
+ # that are within this scope.
762
+ #
763
+ # WARNING: This must instantiate all machines in the target class.
764
+ # It will still be slow, at least the first time for each target class.
765
+ # On the upside, the instantiated machines will be cached, so generating
766
+ # this list for other scopes with the same target class will be much
767
+ # much faster.
768
+ # In tests, 1600 Computers took about 7 minutes the first time,
769
+ # but less than 1 second after caching.
770
+ #
771
+ # See also the warning for #in_scope?
772
+ #
773
+ # @return [Hash{Integer => String}]
774
+ #
775
+ ################
776
+ def scoped_machines
777
+ scoped_machines = {}
778
+ @target_class.all_objects(api: container.api).each do |machine|
779
+ scoped_machines[machine.id] = machine.name if in_scope? machine
780
+ end
781
+ scoped_machines
782
+ end
783
+
784
+ # is a given machine is in this scope?
785
+ #
786
+ # For a parameter you may pass either an instantiated
787
+ # JSS::MobileDevice or JSS::Computer, or an identifier for one.
788
+ # If an identifier is passed, it is not instantiated, but an API
789
+ # request is made for just the required subsets of data, thus
790
+ # speeding things up a bit when calling this method many times.
791
+ #
792
+ # WARNING: For scopes that include Jamf Users and Jamf User Groups
793
+ # as targets or exclusions, this method may return an incorrect value.
794
+ # See the discussion in the documentation for the Scopable::Scope class
795
+ # under 'IMPORTANT - Users & User Groups in Targets and Exclusions'
796
+ #
797
+ # NOTE: currently in-range iBeacons are transient, and are not reported
798
+ # to the JSS as inventory data. As such they are ignored in this result.
799
+ # If a scope contains iBeacon limitations or exclusions, it is up to
800
+ # the user to be aware of that when evaluating the meaning of this result.
801
+ #
802
+ # @param machine[Integer, String, JSS::MobileDevice, JSS::Computer]
803
+ # Either an identifier for the machine, or an instantiated object
804
+ #
805
+ # @return [Boolean]
806
+ #
807
+ ##############
808
+ def in_scope?(machine)
809
+ machine_data = fetch_machine_data machine
741
810
 
742
- alias all_targets? all_targets
811
+ a_target?(machine_data) && within_limitations?(machine_data) && !excluded?(machine_data)
812
+ end
743
813
 
744
814
  # Private Instance Methods
745
815
  #####################################
@@ -765,7 +835,7 @@ module JSS
765
835
  # which keys allowed depends on how the item is used...
766
836
  possible_keys =
767
837
  case realm
768
- when :target then @inclusion_keys
838
+ when :target then @target_keys
769
839
  when :limitation then LIMITATIONS
770
840
  when :exclusion then @exclusion_keys
771
841
  else
@@ -781,15 +851,15 @@ module JSS
781
851
 
782
852
  # id will be a string
783
853
  if key == :jamf_ldap_users
784
- id = ident if JSS::User.all_names(:refresh).include?(ident) || JSS::LDAPServer.user_in_ldap?(ident)
854
+ id = ident if JSS::User.all_names(:refresh, api: container.api).include?(ident) || JSS::LDAPServer.user_in_ldap?(ident)
785
855
 
786
856
  # id will be a string
787
857
  elsif key == :ldap_user_groups
788
- id = ident if JSS::LDAPServer.group_in_ldap? ident
858
+ id = ident if JSS::LDAPServer.group_in_ldap? ident, api: container.api
789
859
 
790
860
  # id will be an integer
791
861
  else
792
- id = SCOPING_CLASSES[key].valid_id ident
862
+ id = SCOPING_CLASSES[key].valid_id ident, api: container.api
793
863
  end
794
864
 
795
865
  raise JSS::NoSuchItemError, "No existing #{key} matching '#{ident}'" if error_if_not_found && id.nil?
@@ -809,6 +879,252 @@ module JSS
809
879
  end
810
880
  end
811
881
 
882
+ # The data used by the methods that figure out if a machine is
883
+ # in this scope, a Hash of Hashes. the sub hashes are:
884
+ #
885
+ # general: the 'general' subset
886
+ # location: the 'location' subset
887
+ # group_ids: an Array of the group ids to which the machine belongs.
888
+ #
889
+ # @param machine[Integer, String, JSS::MobileDevice, JSS::Computer]
890
+ # Either an identifier for the machine, or an instantiated object
891
+ #
892
+ # @return
893
+ #
894
+ def fetch_machine_data(machine)
895
+ case machine
896
+ when JSS::Computer
897
+ raise JSS::InvalidDataError, "Targets of this scope must be #{@target_class}" unless @target_class == JSS::Computer
898
+
899
+ general = machine.init_data[:general]
900
+ location = machine.init_data[:location]
901
+ group_ids = group_ids machine.computer_groups
902
+
903
+ # put in standardize place for easier use
904
+ # MDevs already have this at general[:managed]
905
+ general[:managed] = general[:remote_management][:managed]
906
+
907
+ when JSS::MobileDevice
908
+ raise JSS::InvalidDataError, "Targets of this scope must be #{@target_class}" unless @target_class == JSS::MobileDevice
909
+
910
+ general = machine.init_data[:general]
911
+ location = machine.init_data[:location]
912
+ group_ids = group_ids machine.mobile_device_groups
913
+
914
+ else
915
+ general, location, group_ids = fetch_subsets(machine)
916
+ end # case
917
+
918
+ {
919
+ general: general,
920
+ location: location,
921
+ group_ids: group_ids
922
+ }
923
+ end
924
+
925
+ # When we are given an indentifier for a machine,
926
+ # fetch just the subsets of API data we need to
927
+ # determine if the machine is in this scope
928
+ #
929
+ # @param ident[String, Integer]
930
+ #
931
+ # @return [Array] the general, locacation, and parsed group IDs
932
+ #
933
+ def fetch_subsets(ident)
934
+ id = @target_class.valid_id ident, api: container.api
935
+ raise JSS::NoSuchItemError, "No #{@target_class} matching #{machine}" unless id
936
+
937
+ if @target_class == JSS::MobileDevice
938
+ grp_subset = 'MobileDeviceGroups'
939
+ top_key = :mobile_device
940
+ else
941
+ grp_subset = 'GroupsAccounts'
942
+ top_key = :computer
943
+ end
944
+ subset_rsrc = "#{@target_class::RSRC_BASE}/id/#{id}/subset/General&Location&#{grp_subset}"
945
+ data = container.api.get_rsrc(subset_rsrc)[top_key]
946
+ grp_data =
947
+ if @target_class == JSS::MobileDevice
948
+ data[:mobile_device_groups]
949
+ else
950
+ data[:groups_accounts][:computer_group_memberships]
951
+ end
952
+
953
+ [data[:general], data[:location], group_ids(grp_data)]
954
+ end
955
+
956
+ # Given the raw API data for a machines group membership,
957
+ # return an array of the IDs of the groups.
958
+ #
959
+ # @param raw[Array] The API array of the machine's group memberships
960
+ #
961
+ # @return [Array] The ID's of the groups to which the machine belongs.
962
+ #
963
+ def group_ids(raw)
964
+ if @target_class == JSS::MobileDevice
965
+ raw.map { |mdg| mdg[:id] }
966
+ else
967
+ names_to_ids = @group_class.map_all_ids_to(:name).invert
968
+ raw.map { |gn| names_to_ids[gn] }
969
+ end
970
+ end
971
+
972
+ # @param machine_data[Hash] See #fetch_machine_data
973
+ # @return [Boolean]
974
+ ################
975
+ def a_target?(machine_data)
976
+ return false unless machine_data[:general][:managed]
977
+ return true if \
978
+ all_targets? || \
979
+ machine_directly_scoped?(machine_data, :target) || \
980
+ machine_in_scope_group?(machine_data, :target) || \
981
+ machine_in_scope_buildings?(machine_data, :target) || \
982
+ machine_in_scope_depts?(machine_data, :target)
983
+
984
+ false
985
+ end
986
+
987
+ # @param machine_data[Hash] See #fetch_machine_data
988
+ # @return [Boolean]
989
+ ################
990
+ def within_limitations?(machine_data)
991
+ return false if \
992
+ machine_in_scope_netsegs?(machine_data, :limitation) == false || \
993
+ machine_in_scope_jamf_ldap_users_list?(machine_data, :limitation) == false || \
994
+ machine_in_scope_ldap_usergroup_list?(machine_data, :limitation) == false
995
+
996
+ true
997
+ end
998
+
999
+ # @param machine_data[Hash] See #fetch_machine_data
1000
+ # @return [Boolean]
1001
+ ################
1002
+ def excluded?(machine_data)
1003
+ return true if
1004
+ machine_directly_scoped?(machine_data, :exclusion) || \
1005
+ machine_in_scope_group?(machine_data, :exclusion) || \
1006
+ machine_in_scope_buildings?(machine_data, :exclusion) || \
1007
+ machine_in_scope_depts?(machine_data, :exclusion) || \
1008
+ machine_in_scope_netsegs?(machine_data, :exclusion) || \
1009
+ machine_in_scope_jamf_ldap_users_list?(machine_data, :exclusion) || \
1010
+ machine_in_scope_ldap_usergroup_list?(machine_data, :exclusion)
1011
+
1012
+ false
1013
+ end
1014
+
1015
+ # @param machine_data[Hash] See #fetch_machine_data
1016
+ # @param part[Symbol] either :target or :exclusion
1017
+ # @return [Boolean] Is the machine directly spcified in this part of the scope?
1018
+ ################
1019
+ def machine_directly_scoped?(machine_data, part)
1020
+ scope_list = part == :target ? @targets[:direct_targets] : @exclusions[:direct_exclusions]
1021
+ scope_list.include? machine_data[:general][:id]
1022
+ end
1023
+
1024
+ # @param machine_data[Hash] See #fetch_machine_data
1025
+ # @param part[Symbol] either :target or :exclusion
1026
+ # @return [Boolean] Is the machine a member of any group listed in this part of the scope?
1027
+ ################
1028
+ def machine_in_scope_group?(machine_data, part)
1029
+ scope_list = part == :target ? @targets[:group_targets] : @exclusions[:group_exclusions]
1030
+ # if the list is empty, return nil
1031
+ return if scope_list.empty?
1032
+
1033
+ # if the intersection of the machine's group ids, and those of the scope part
1034
+ # is not empty, then the machine is in at least one of the groups
1035
+ !(machine_data[:group_ids] & scope_list).empty?
1036
+ end
1037
+
1038
+ # @param machine_data[Hash] See #fetch_machine_data
1039
+ # @param part[Symbol] either :target or :exclusion
1040
+ # @return [Boolean] Is the machine in any building listed in this part of the scope?
1041
+ #################
1042
+ def machine_in_scope_buildings?(machine_data, part)
1043
+ scope_list = part == :target ? @targets[:buildings] : @exclusions[:buildings]
1044
+
1045
+ # nil if empty
1046
+ return if scope_list.empty?
1047
+ # false if no building for the machine - it isn't in any dept
1048
+ return false if machine_data[:location][:building].to_s.empty?
1049
+
1050
+ building_id = JSS::Building.map_all_ids_to(:name).invert[machine_data[:location][:building]]
1051
+ scope_list.include? building_id
1052
+ end
1053
+
1054
+ # @param machine_data[Hash] See #fetch_machine_data
1055
+ # @param part[Symbol] either :target or :exclusion
1056
+ # @return [Boolean] Is the machine in any department listed in this part of the scope?
1057
+ #################
1058
+ def machine_in_scope_depts?(machine_data, part)
1059
+ scope_list = part == :target ? @targets[:departments] : @exclusions[:departments]
1060
+
1061
+ # nil if empty
1062
+ return if scope_list.empty?
1063
+ # false if no dept for the machine - it isn't in any dept
1064
+ return false if machine_data[:location][:department].to_s.empty?
1065
+
1066
+ dept_id = JSS::Department.map_all_ids_to(:name).invert[machine_data[:location][:department]]
1067
+
1068
+ scope_list.include? dept_id
1069
+ end
1070
+
1071
+ # @param machine_data[Hash] See #fetch_machine_data
1072
+ # @param part[Symbol] either :limitation or :exclusion
1073
+ # @return [Boolean] Is the machine in any NetworkSegment listed in this part of the scope?
1074
+ ##################
1075
+ def machine_in_scope_netsegs?(machine_data, part)
1076
+ scope_list = part == :limitation ? @limitations[:network_segments] : @exclusions[:network_segments]
1077
+
1078
+ # nil if no netsegs in scope part
1079
+ return if scope_list.empty?
1080
+
1081
+ ip = @target_class == JSS::Computer ? machine_data[:general][:last_reported_ip] : machine_data[:general][:ip_address]
1082
+ # false if no ip for machine - it isn't in a any of the segs
1083
+ return false if ip.to_s.empty?
1084
+
1085
+ mach_segs = JSS::NetworkSegment.network_segments_for_ip ip
1086
+
1087
+ # if the intersection is not empty, then the machine is in at least one of the net segs
1088
+ !(mach_segs & scope_list).empty?
1089
+ end
1090
+
1091
+ # @param machine_data[Hash] See #fetch_machine_data
1092
+ # @param part[Symbol] either :limitation or :exclusion
1093
+ # @return [Boolean] Is the user of this machine in the list of jamf/ldap users in this part of the scope?
1094
+ ##################
1095
+ def machine_in_scope_jamf_ldap_users_list?(machine_data, part)
1096
+ scope_list = part == :limitation ? @limitations[:jamf_ldap_users] : @exclusions[:jamf_ldap_users]
1097
+
1098
+ # nil if the list is empty
1099
+ return if scope_list.empty?
1100
+
1101
+ scope_list.include? machine_data[:location][:username]
1102
+ end
1103
+
1104
+ # @param machine_data[Hash] See #fetch_machine_data
1105
+ # @param part[Symbol] either :limitation or :exclusion
1106
+ # @return [Boolean] Is the user of this machine a member of any of the LDAP groups in in this part of the scope?
1107
+ ##################
1108
+ def machine_in_scope_ldap_usergroup_list?(machine_data, part)
1109
+ scope_list = part == :limitation ? @limitations[:ldap_user_groups] : @exclusions[:ldap_user_groups]
1110
+
1111
+ # nil if the list is empty
1112
+ return if scope_list.empty?
1113
+
1114
+ # loop thru them checking to see if the user is a member
1115
+ scope_list.each do |ldapgroup|
1116
+ server = JSS::LDAPServer.server_for_group ldapgroup
1117
+ # if the group doesn't exist in any LDAP the user isn't a part of it
1118
+ next unless server
1119
+
1120
+ # if the user name is in any group, return true
1121
+ return true if JSS::LDAPServer.check_membership server, machine_data[:location][:username], ldapgroup
1122
+ end
1123
+
1124
+ # if we're here, not in any group
1125
+ false
1126
+ end
1127
+
812
1128
  end # class Scope
813
1129
 
814
1130
  end # module Scopable