ruby-jss 1.3.3 → 1.6.0b1

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.

Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +142 -0
  3. data/THANKS.md +3 -2
  4. data/lib/jamf.rb +18 -17
  5. data/lib/jamf/api/base_classes/collection_resource.rb +613 -0
  6. data/lib/jamf/api/{abstract_classes → base_classes}/json_object.rb +109 -101
  7. data/lib/jamf/api/{abstract_classes → base_classes}/prestage.rb +55 -30
  8. data/lib/jamf/api/{abstract_classes → base_classes}/resource.rb +10 -6
  9. data/lib/jamf/api/{abstract_classes → base_classes}/singleton_resource.rb +4 -3
  10. data/lib/jamf/api/connection.rb +13 -9
  11. data/lib/jamf/api/connection/api_error.rb +8 -8
  12. data/lib/jamf/api/connection/token.rb +36 -15
  13. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  14. data/lib/jamf/api/json_objects/{location.rb → device_enrollment_device_sync_state.rb} +27 -41
  15. data/lib/jamf/api/json_objects/device_enrollment_sync_status.rb +1 -1
  16. data/lib/jamf/api/json_objects/{attachment.rb → locale.rb} +14 -23
  17. data/lib/jamf/api/json_objects/md_prestage_name.rb +1 -1
  18. data/lib/jamf/api/json_objects/md_prestage_names.rb +2 -2
  19. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  20. data/lib/jamf/api/json_objects/prestage_assignment.rb +2 -2
  21. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  22. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  23. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  24. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  25. data/lib/jamf/api/mixins/{abstract.rb → base_class.rb} +34 -16
  26. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  27. data/lib/jamf/api/mixins/change_log.rb +201 -51
  28. data/lib/jamf/api/{resources/collection_resources/computer.rb → mixins/filterable.rb} +19 -17
  29. data/lib/jamf/api/mixins/pageable.rb +208 -0
  30. data/lib/jamf/api/{json_objects/installed_application.rb → mixins/sortable.rb} +33 -33
  31. data/lib/jamf/api/resources/collection_resources/building.rb +16 -9
  32. data/lib/jamf/api/resources/collection_resources/category.rb +5 -4
  33. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +12 -5
  34. data/lib/jamf/api/resources/collection_resources/department.rb +1 -3
  35. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  36. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  37. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +25 -23
  38. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  39. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  40. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  41. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  42. data/lib/jamf/client.rb +3 -3
  43. data/lib/jamf/client/management_action.rb +2 -3
  44. data/lib/jamf/composer.rb +2 -2
  45. data/lib/jamf/utility.rb +35 -7
  46. data/lib/jamf/validate.rb +63 -24
  47. data/lib/jamf/version.rb +1 -1
  48. data/lib/jss.rb +2 -2
  49. data/lib/jss/api_connection.rb +114 -406
  50. data/lib/jss/api_object.rb +10 -20
  51. data/lib/jss/api_object/advanced_search.rb +27 -26
  52. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  53. data/lib/jss/api_object/categorizable.rb +1 -1
  54. data/lib/jss/api_object/computer.rb +13 -0
  55. data/lib/jss/api_object/configuration_profile.rb +61 -5
  56. data/lib/jss/api_object/directory_binding_type.rb +66 -60
  57. data/lib/jss/api_object/directory_binding_type/active_directory.rb +71 -34
  58. data/lib/jss/api_object/directory_binding_type/admitmac.rb +536 -467
  59. data/lib/jss/api_object/directory_binding_type/centrify.rb +21 -7
  60. data/lib/jss/api_object/directory_binding_type/open_directory.rb +4 -4
  61. data/lib/jss/api_object/distribution_point.rb +2 -2
  62. data/lib/jss/api_object/dock_item.rb +102 -96
  63. data/lib/jss/api_object/ebook.rb +1 -2
  64. data/lib/jss/api_object/extendable.rb +1 -1
  65. data/lib/jss/api_object/extension_attribute.rb +4 -3
  66. data/lib/jss/api_object/group.rb +33 -2
  67. data/lib/jss/api_object/mac_application.rb +107 -8
  68. data/lib/jss/api_object/network_segment.rb +45 -13
  69. data/lib/jss/api_object/patch_source.rb +10 -9
  70. data/lib/jss/api_object/policy.rb +267 -28
  71. data/lib/jss/api_object/printer.rb +10 -4
  72. data/lib/jss/api_object/scopable.rb +10 -15
  73. data/lib/jss/api_object/scopable/scope.rb +389 -73
  74. data/lib/jss/api_object/script.rb +242 -352
  75. data/lib/jss/api_object/self_servable.rb +17 -9
  76. data/lib/jss/api_object/uploadable.rb +1 -1
  77. data/lib/jss/api_object/user.rb +43 -2
  78. data/lib/jss/api_object/vpp_account.rb +209 -0
  79. data/lib/jss/api_object/vppable.rb +169 -13
  80. data/lib/jss/client/management_action.rb +1 -2
  81. data/lib/jss/composer.rb +2 -2
  82. data/lib/jss/exceptions.rb +3 -0
  83. data/lib/jss/server.rb +15 -0
  84. data/lib/jss/utility.rb +213 -45
  85. data/lib/jss/validate.rb +53 -10
  86. data/lib/jss/version.rb +1 -1
  87. metadata +50 -66
  88. data/lib/jamf/api/abstract_classes/advanced_search.rb +0 -86
  89. data/lib/jamf/api/abstract_classes/collection_resource.rb +0 -433
  90. data/lib/jamf/api/abstract_classes/generic_reference.rb +0 -145
  91. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +0 -126
  92. data/lib/jamf/api/json_objects/account_prefs.rb +0 -79
  93. data/lib/jamf/api/json_objects/android_details.rb +0 -139
  94. data/lib/jamf/api/json_objects/appletv_details.rb +0 -110
  95. data/lib/jamf/api/json_objects/cellular_network.rb +0 -151
  96. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +0 -67
  97. data/lib/jamf/api/json_objects/criterion.rb +0 -152
  98. data/lib/jamf/api/json_objects/extension_attribute_value.rb +0 -128
  99. data/lib/jamf/api/json_objects/installed_certificate.rb +0 -53
  100. data/lib/jamf/api/json_objects/installed_configuration_profile.rb +0 -67
  101. data/lib/jamf/api/json_objects/installed_ebook.rb +0 -58
  102. data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +0 -59
  103. data/lib/jamf/api/json_objects/ios_details.rb +0 -244
  104. data/lib/jamf/api/json_objects/mobile_device_details.rb +0 -219
  105. data/lib/jamf/api/json_objects/mobile_device_security.rb +0 -101
  106. data/lib/jamf/api/json_objects/purchasing_data.rb +0 -125
  107. data/lib/jamf/api/mixins/locatable.rb +0 -124
  108. data/lib/jamf/api/mixins/referable.rb +0 -92
  109. data/lib/jamf/api/resources/collection_resources/account.rb +0 -163
  110. data/lib/jamf/api/resources/collection_resources/advanced_mobile_device_search.rb +0 -52
  111. data/lib/jamf/api/resources/collection_resources/advanced_user_search.rb +0 -52
  112. data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +0 -45
  113. data/lib/jamf/api/resources/collection_resources/mobile_device.rb +0 -315
  114. data/lib/jamf/api/resources/collection_resources/site.rb +0 -77
  115. data/lib/jamf/api/resources/singleton_resources/authorization.rb +0 -88
  116. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +0 -139
  117. 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