ruby-jss 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

@@ -23,12 +23,11 @@
23
23
  ###
24
24
  ###
25
25
 
26
- # This is just a stub for now.
27
26
 
28
27
  #
29
28
  module JSS
30
29
 
31
- #
30
+ # This is just a stub for now.
32
31
  class EBook < APIObject
33
32
 
34
33
  include Sitable
@@ -179,9 +179,10 @@ module JSS
179
179
  # deprecated - no longer in the UI
180
180
  @recon_display = @init_data[:recon_display] || @web_display
181
181
 
182
- # the name of the EA might have spaces and caps, which the will come to us as symbols with the spaces
183
- # as underscores, like this.
184
- @symbolized_name = @name.gsub(/-| /, '_').to_sym
182
+ # When used in Advanced Search results, the EA name
183
+ # has colons removed, spaces & dashes turned to underscores.
184
+ # and then ruby-jss symbolizes the name.
185
+ @symbolized_name = @name.gsub(':', '').gsub(/-| /, '_').to_sym
185
186
  end # init
186
187
 
187
188
  # Public Instance Methods
@@ -22,15 +22,19 @@
22
22
  ### language governing permissions and limitations under the Apache License.
23
23
  ###
24
24
 
25
- # This is just a stub for now.
26
-
27
- #
28
25
  module JSS
29
26
 
30
- #
31
- class MacApplication < APIObject
27
+ # This is just a stub for now.
28
+ class MacApplication < JSS::APIObject
32
29
 
33
- include Sitable
30
+ # Mix-Ins
31
+ #####################################
32
+ include JSS::Updatable
33
+ include JSS::Scopable
34
+ include JSS::SelfServable
35
+ include JSS::Categorizable
36
+ include JSS::VPPable
37
+ include JSS::Sitable
34
38
 
35
39
  ### The base for REST resources of this class
36
40
  RSRC_BASE = 'macapplications'.freeze
@@ -47,9 +51,104 @@ module JSS
47
51
  # See {APIObject#add_object_history_entry}
48
52
  OBJECT_HISTORY_OBJECT_TYPE = 350
49
53
 
54
+ # See JSS::Scopable
55
+ SCOPE_TARGET_KEY = :computers
56
+
57
+ # Where is the Category in the API JSON?
58
+ CATEGORY_SUBSET = :general
59
+
60
+ # How is the category stored in the API data?
61
+ CATEGORY_DATA_TYPE = Hash
62
+
50
63
  # Where is the Site data in the API JSON?
51
64
  SITE_SUBSET = :general
52
65
 
53
- end
66
+ # Attributes
67
+ #############################################
68
+
69
+ # @return [String]
70
+ attr_reader :version
71
+
72
+ # @return [Boolean]
73
+ attr_reader :is_free
74
+ alias free? is_free
75
+
76
+ # @return [String]
77
+ attr_reader :bundle_id
78
+
79
+ # @return [String]
80
+ attr_reader :url
81
+
82
+ ## Constructor
83
+ #####################################
84
+
85
+ def initialize(args = {})
86
+ super
87
+ general = @init_data[:general]
88
+ @version = general[:version]
89
+ @is_free = general[:is_free]
90
+ @bundle_id = general[:bundle_id]
91
+ @url = general[:url]
92
+ end
93
+
94
+ # Overrides, because consistency isn't alway a thing in the
95
+ # classic API
96
+ #############################################
97
+
98
+ # Override self_service_display_name getter
99
+ def self_service_display_name
100
+ raise JSS::UnsupportedError, 'MacApplications do not have separate display names. Please use the object name.'
101
+ end
102
+
103
+ # Override self_service_display_name setter
104
+ def self_service_display_name=(_newname)
105
+ raise JSS::UnsupportedError, 'MacApplications do not have separate display names. Please use the object name.'
106
+ end
107
+
108
+ # Override reinstall_button_text getter
109
+ def reinstall_button_text
110
+ raise JSS::UnsupportedError, 'MacApplications do not have separate text for reinstall buttons. Please use install_button_text.'
111
+ end
112
+
113
+ # Override reinstall_button_text setter
114
+ def reinstall_button_text=(_new)
115
+ raise JSS::UnsupportedError, 'MacApplications do not have separate text for reinstall buttons. Please use install_button_text.'
116
+ end
117
+
118
+ # Alas, SSvc icons are not uploadable via the API for
119
+ # mac apps
120
+ def upload(_type, _local_file)
121
+ raise JSS::UnsupportedError, 'The Classic API does not support uploading icons for MacApplications. Please use the Web UI'
122
+ end
123
+
124
+ ## Private Instance Methods
125
+ #####################################
126
+ private
127
+
128
+ ### Return the xml for creating or updating this script in the JSS
129
+ ###
130
+ def rest_xml
131
+ doc = REXML::Document.new APIConnection::XML_HEADER
132
+ obj = doc.add_element RSRC_OBJECT_KEY.to_s
133
+
134
+ general = obj.add_element('general')
135
+
136
+ general.add_element('version').text = @version
137
+ general.add_element('is_free').text = @is_free.to_s
138
+ general.add_element('bundle_id').text = @bundle_id
139
+ general.add_element('url').text = @url
140
+
141
+ obj << @scope.scope_xml
142
+
143
+ add_self_service_xml doc
144
+ add_category_to_xml doc
145
+ add_site_to_xml doc
146
+ add_vpp_xml doc
147
+
148
+ doc.to_s
149
+
150
+ end
151
+
152
+ end # class MacApplication
54
153
 
55
- end
154
+ end # Module JSS
@@ -148,6 +148,12 @@ module JSS
148
148
  monthly: 'Once every month'
149
149
  }.freeze
150
150
 
151
+ RETRY_EVENTS = {
152
+ none: 'none',
153
+ checkin: 'check-in',
154
+ trigger: 'trigger'
155
+ }.freeze
156
+
151
157
  RESTART_WHEN = {
152
158
  if_pkg_requires: 'Restart if a package or update requires it',
153
159
  now: 'Restart immediately',
@@ -613,7 +619,6 @@ module JSS
613
619
 
614
620
  if @in_jss
615
621
  gen = @init_data[:general]
616
- @frequency = gen[:frequency]
617
622
  @target_drive = gen[:target_drive]
618
623
  @offline = gen[:offline]
619
624
  @enabled = gen[:enabled]
@@ -629,6 +634,10 @@ module JSS
629
634
  trigger_enrollment_complete: gen[:trigger_enrollment_complete],
630
635
  trigger_other: gen[:trigger_other]
631
636
  }
637
+ @frequency = gen[:frequency]
638
+ @retry_event = gen[:retry_event]
639
+ @retry_attempts = gen[:retry_attempts]
640
+ @notify_failed_retries = gen[:notify_on_each_failed_retry]
632
641
 
633
642
  dtl = gen[:date_time_limitations]
634
643
 
@@ -745,8 +754,93 @@ module JSS
745
754
  # @return [void]
746
755
  #
747
756
  def frequency=(freq)
748
- raise JSS::InvalidDataError, "New frequency must be one of :#{FREQUENCIES.keys.join ', :'}" unless FREQUENCIES.key?(freq)
749
- @frequency = FREQUENCIES[freq]
757
+ raise JSS::InvalidDataError, "New frequency must be one of :#{FREQUENCIES.keys.join ', :'}" unless FREQUENCIES.key?(freq) || FREQUENCIES.value?(freq)
758
+
759
+ freq = freq.is_a?(Symbol) ? FREQUENCIES[freq] : freq
760
+ return if freq == @frequency
761
+
762
+ @frequency = freq
763
+ @need_to_update = true
764
+ end
765
+
766
+ # @return [String] The event that causes a policy retry
767
+ def retry_event
768
+ return RETRY_EVENTS[:none] unless FREQUENCIES[:once_per_computer] == @frequency
769
+
770
+ @retry_event
771
+ end
772
+
773
+ # Set the event that causes a retry if the policy fails.
774
+ # One of the ways to turn off policy retry is to set this to :none
775
+ # The other is to set the retry_attempts to 0
776
+ #
777
+ # @param [Symbol, String] A key or value from RETRY_EVENTS
778
+ # @return [void]
779
+ #
780
+ def retry_event=(evt)
781
+ validate_retry_opt
782
+ raise JSS::InvalidDataError, "Retry event must be one of :#{RETRY_EVENTS.keys.join ', :'}" unless RETRY_EVENTS.key?(evt) || RETRY_EVENTS.value?(evt)
783
+
784
+ evt = evt.is_a?(Symbol) ? RETRY_EVENTS[evt] : evt
785
+ return if evt == @retry_event
786
+
787
+ # if the event is not 'none' and attempts is <= 0,
788
+ # set events to 1, or the API won't accept it
789
+ unless evt == RETRY_EVENTS[:none]
790
+ @retry_attempts = 1 unless @retry_attempts.positive?
791
+ end
792
+
793
+ @retry_event = evt
794
+ @need_to_update = true
795
+ end
796
+
797
+ # @return [Integer] How many times wil the policy be retried if it fails.
798
+ # -1 means no retries, otherwise, an integer from 1 to 10
799
+ def retry_attempts
800
+ return 0 unless FREQUENCIES[:once_per_computer] == @frequency
801
+
802
+ @retry_attempts
803
+ end
804
+
805
+ # Set the number of times to retry if the policy fails.
806
+ # One of the ways to turn off policy retry is to set this to 0 or -1
807
+ # The other is to set retry_event to :none
808
+ #
809
+ # @param [Integer] From -1 to 10
810
+ # @return [void]
811
+ #
812
+ def retry_attempts=(int)
813
+ validate_retry_opt
814
+ raise JSS::InvalidDataError, 'Retry attempts must be an integer from 0-10' unless int.is_a?(Integer) && (-1..10).include?(int)
815
+
816
+ # if zero or -1, turn off retries
817
+ if int <= 0
818
+ @retry_event = RETRY_EVENTS[:none]
819
+ int = -1
820
+ end
821
+ return if @retry_attempts == int
822
+
823
+ @retry_attempts = int
824
+ @need_to_update = true
825
+ end
826
+
827
+ # @return [Boolean] Should admins be notified of failed retry attempts
828
+ def notify_failed_retries?
829
+ return false unless FREQUENCIES[:once_per_computer] == @frequency
830
+
831
+ @notify_failed_retries
832
+ end
833
+ alias notify_failed_retries notify_failed_retries?
834
+ alias notify_on_each_failed_retry notify_failed_retries?
835
+
836
+ # @param bool[Boolean] Should admins be notified of failed retry attempts
837
+ # @return [void]
838
+ def notify_failed_retries=(bool)
839
+ validate_retry_opt
840
+ bool = JSS::Validate.boolean bool
841
+ return if @notify_failed_retries == bool
842
+
843
+ @notify_failed_retries = bool
750
844
  @need_to_update = true
751
845
  end
752
846
 
@@ -1656,6 +1750,17 @@ module JSS
1656
1750
 
1657
1751
  private
1658
1752
 
1753
+ # raise an error if a trying to set retry options when
1754
+ # frequency is not 'once per comptuer'
1755
+ #
1756
+ # @return [void]
1757
+ #
1758
+ def validate_retry_opt
1759
+ return if FREQUENCIES[:once_per_computer] == @frequency
1760
+
1761
+ raise JSS::UnsupportedError, 'Policy retry is only available when frequency is set to :once_per_computer'
1762
+ end
1763
+
1659
1764
  # raise an error if a package being added isn't valid
1660
1765
  #
1661
1766
  # @see #add_package
@@ -1786,6 +1891,10 @@ module JSS
1786
1891
  general.add_element('name').text = @name
1787
1892
  general.add_element('enabled').text = @enabled
1788
1893
  general.add_element('frequency').text = @frequency
1894
+ general.add_element('retry_event').text = @retry_event
1895
+ general.add_element('retry_attempts').text = @retry_attempts.to_s
1896
+ general.add_element('notify_on_each_failed_retry').text = @notify_failed_retries.to_s
1897
+
1789
1898
  general.add_element('target_drive').text = @target_drive
1790
1899
  general.add_element('offline').text = @offline
1791
1900
 
@@ -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.
@@ -226,56 +230,64 @@ module JSS
226
230
  # if we can't connect to LDAP servers for verification?
227
231
  attr_accessor :unable_to_verify_ldap_entries
228
232
 
229
- # what type of target is this scope for? Computers or Mobiledevices?
233
+ # what type of target is this scope for? Computers or MobileDevices?
230
234
  attr_reader :target_class
231
235
 
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
236
+ # what type of target group is this scope for? ComputerGroups or MobileDeviceGroups?
237
+ attr_reader :group_class
245
238
 
246
239
  # @return [Boolean]
247
240
  #
248
241
  # Does this scope cover all targets?
249
242
  #
250
- # If this is true, the @inclusions Hash is ignored, and all
243
+ # If this is true, the @targets Hash is ignored, and all
251
244
  # targets in the JSS form the base scope.
252
245
  #
253
246
  attr_reader :all_targets
247
+ alias all_targets? all_targets
248
+
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.
261
+ #
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 .
258
268
  #
259
- # The arrays of names are:
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>]
267
- #
268
- # The items in these arrays are the exclusions applied to targets in the @inclusions .
277
+ # The items in these arrays are the exclusions applied to targets in the @targets .
269
278
  #
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 = {}
@@ -428,9 +444,9 @@ 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
451
  @container.should_update if @container
436
452
  end # sinclude_in_scope
@@ -458,11 +474,11 @@ 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] && @exclusions[key].include?(item_id)
462
478
 
463
479
  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)
464
480
 
465
- @inclusions[key] << item_id
481
+ @targets[key] << item_id
466
482
  @all_targets = false
467
483
  @container.should_update if @container
468
484
  end
@@ -483,9 +499,9 @@ 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] && @exclusions[key].include?(item_id)
487
503
 
488
- @inclusions[key].delete item_id
504
+ @targets[key].delete item_id
489
505
  @container.should_update if @container
490
506
  end
491
507
  alias remove_inclusion remove_target
@@ -600,8 +616,8 @@ 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
+ raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @targets[key] && @exclusions[key].include?(item_id)
605
621
  when *LIMITATIONS
606
622
  if @limitations[key] && @exclusions[key].include?(item_id)
607
623
  raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already an explicit limitation."
@@ -634,7 +650,7 @@ module JSS
634
650
  item_id = validate_item(:exclusion, key, item)
635
651
  return if @exclusions[key] && @exclusions[key].include?(item_id)
636
652
 
637
- raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @inclusions[key] && @inclusions[key].include?(item)
653
+ raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @targets[key] && @targets[key].include?(item)
638
654
 
639
655
  raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key] && @limitations[key].include?(item)
640
656
 
@@ -672,7 +688,8 @@ module JSS
672
688
  scope = REXML::Element.new 'scope'
673
689
  scope.add_element(@all_key.to_s).text = @all_targets
674
690
 
675
- @inclusions.each do |klass, list|
691
+ @target_keys.each do |klass|
692
+ list = @targets[klass]
676
693
  list.compact!
677
694
  list.delete 0
678
695
  list_as_hashes = list.map { |i| { id: i } }
@@ -702,7 +719,8 @@ module JSS
702
719
  end
703
720
 
704
721
  exclusions = scope.add_element('exclusions')
705
- @exclusions.each do |klass, list|
722
+ @exclusion_keys.each do |klass|
723
+ list = @exclusions[klass]
706
724
  list.compact!
707
725
  list.delete 0
708
726
  if klass == :jamf_ldap_users
@@ -737,9 +755,60 @@ module JSS
737
755
  vars
738
756
  end
739
757
 
740
- # Aliases
758
+ # Return a hash of id => name for all machines in the target class
759
+ # that are within this scope.
760
+ #
761
+ # WARNING: This must instantiate all machines in the target class.
762
+ # It will still be slow, at least the first time for each target class.
763
+ # On the upside, the instantiated machines will be cached, so generating
764
+ # this list for other scopes with the same target class will be much
765
+ # much faster.
766
+ # In tests, 1600 Computers took about 7 minutes the first time,
767
+ # but less than 1 second after caching.
768
+ #
769
+ # See also the warning for #in_scope?
770
+ #
771
+ # @return [Hash{Integer => String}]
772
+ #
773
+ ################
774
+ def scoped_machines
775
+ scoped_machines = {}
776
+ @target_class.all_objects.each do |machine|
777
+ scoped_machines[machine.id] = machine.name if in_scope? machine
778
+ end
779
+ scoped_machines
780
+ end
781
+
782
+ # is a given machine is in this scope?
783
+ #
784
+ # For a parameter you may pass either an instantiated
785
+ # JSS::MobileDevice or JSS::Computer, or an identifier for one.
786
+ # If an identifier is passed, it is not instantiated, but an API
787
+ # request is made for just the required subsets of data, thus
788
+ # speeding things up a bit when calling this method many times.
789
+ #
790
+ # WARNING: For scopes that include Jamf Users and Jamf User Groups
791
+ # as targets or exclusions, this method may return an incorrect value.
792
+ # See the discussion in the documentation for the Scopable::Scope class
793
+ # under 'IMPORTANT - Users & User Groups in Targets and Exclusions'
794
+ #
795
+ # NOTE: currently in-range iBeacons are transient, and are not reported
796
+ # to the JSS as inventory data. As such they are ignored in this result.
797
+ # If a scope contains iBeacon limitations or exclusions, it is up to
798
+ # the user to be aware of that when evaluating the meaning of this result.
799
+ #
800
+ # @param machine[Integer, String, JSS::MobileDevice, JSS::Computer]
801
+ # Either an identifier for the machine, or an instantiated object
802
+ #
803
+ # @return [Boolean]
804
+ #
805
+ ##############
806
+ def in_scope?(machine)
807
+ machine_data = fetch_machine_data machine
808
+
809
+ a_target?(machine_data) && within_limitations?(machine_data) && !excluded?(machine_data)
810
+ end
741
811
 
742
- alias all_targets? all_targets
743
812
 
744
813
  # Private Instance Methods
745
814
  #####################################
@@ -765,7 +834,7 @@ module JSS
765
834
  # which keys allowed depends on how the item is used...
766
835
  possible_keys =
767
836
  case realm
768
- when :target then @inclusion_keys
837
+ when :target then @target_keys
769
838
  when :limitation then LIMITATIONS
770
839
  when :exclusion then @exclusion_keys
771
840
  else
@@ -809,6 +878,252 @@ module JSS
809
878
  end
810
879
  end
811
880
 
881
+ # The data used by the methods that figure out if a machine is
882
+ # in this scope, a Hash of Hashes. the sub hashes are:
883
+ #
884
+ # general: the 'general' subset
885
+ # location: the 'location' subset
886
+ # group_ids: an Array of the group ids to which the machine belongs.
887
+ #
888
+ # @param machine[Integer, String, JSS::MobileDevice, JSS::Computer]
889
+ # Either an identifier for the machine, or an instantiated object
890
+ #
891
+ # @return
892
+ #
893
+ def fetch_machine_data(machine)
894
+ case machine
895
+ when JSS::Computer
896
+ raise JSS::InvalidDataError, "Targets of this scope must be #{@target_class}" unless @target_class == JSS::Computer
897
+
898
+ general = machine.init_data[:general]
899
+ location = machine.init_data[:location]
900
+ group_ids = group_ids machine.computer_groups
901
+
902
+ # put in standardize place for easier use
903
+ # MDevs already have this at general[:managed]
904
+ general[:managed] = general[:remote_management][:managed]
905
+
906
+ when JSS::MobileDevice
907
+ raise JSS::InvalidDataError, "Targets of this scope must be #{@target_class}" unless @target_class == JSS::MobileDevice
908
+
909
+ general = machine.init_data[:general]
910
+ location = machine.init_data[:location]
911
+ group_ids = group_ids machine.mobile_device_groups
912
+
913
+ else
914
+ general, location, group_ids = fetch_subsets(machine)
915
+ end # case
916
+
917
+ {
918
+ general: general,
919
+ location: location,
920
+ group_ids: group_ids
921
+ }
922
+ end
923
+
924
+ # When we are given an indentifier for a machine,
925
+ # fetch just the subsets of API data we need to
926
+ # determine if the machine is in this scope
927
+ #
928
+ # @param ident[String, Integer]
929
+ #
930
+ # @return [Array] the general, locacation, and parsed group IDs
931
+ #
932
+ def fetch_subsets(ident)
933
+ id = @target_class.valid_id ident
934
+ raise JSS::NoSuchItemError, "No #{@target_class} matching #{machine}" unless id
935
+
936
+ if @target_class == JSS::MobileDevice
937
+ grp_subset = 'MobileDeviceGroups'
938
+ top_key = :mobile_device
939
+ else
940
+ grp_subset = 'GroupsAccounts'
941
+ top_key = :computer
942
+ end
943
+ subset_rsrc = "#{@target_class::RSRC_BASE}/id/#{id}/subset/General&Location&#{grp_subset}"
944
+ data = container.api.get_rsrc(subset_rsrc)[top_key]
945
+ grp_data =
946
+ if @target_class == JSS::MobileDevice
947
+ data[:mobile_device_groups]
948
+ else
949
+ data[:groups_accounts][:computer_group_memberships]
950
+ end
951
+
952
+ [data[:general], data[:location], group_ids(grp_data)]
953
+ end
954
+
955
+ # Given the raw API data for a machines group membership,
956
+ # return an array of the IDs of the groups.
957
+ #
958
+ # @param raw[Array] The API array of the machine's group memberships
959
+ #
960
+ # @return [Array] The ID's of the groups to which the machine belongs.
961
+ #
962
+ def group_ids(raw)
963
+ if @target_class == JSS::MobileDevice
964
+ raw.map { |mdg| mdg[:id] }
965
+ else
966
+ names_to_ids = @group_class.map_all_ids_to(:name).invert
967
+ raw.map { |gn| names_to_ids[gn] }
968
+ end
969
+ end
970
+
971
+ # @param machine_data[Hash] See #fetch_machine_data
972
+ # @return [Boolean]
973
+ ################
974
+ def a_target?(machine_data)
975
+ return false unless machine_data[:general][:managed]
976
+ return true if \
977
+ all_targets? || \
978
+ machine_directly_scoped?(machine_data, :target) || \
979
+ machine_in_scope_group?(machine_data, :target) || \
980
+ machine_in_scope_buildings?(machine_data, :target) || \
981
+ machine_in_scope_depts?(machine_data, :target)
982
+
983
+ false
984
+ end
985
+
986
+ # @param machine_data[Hash] See #fetch_machine_data
987
+ # @return [Boolean]
988
+ ################
989
+ def within_limitations?(machine_data)
990
+ return false if \
991
+ machine_in_scope_netsegs?(machine_data, :limitation) == false || \
992
+ machine_in_scope_jamf_ldap_users_list?(machine_data, :limitation) == false || \
993
+ machine_in_scope_ldap_usergroup_list?(machine_data, :limitation) == false
994
+
995
+ true
996
+ end
997
+
998
+ # @param machine_data[Hash] See #fetch_machine_data
999
+ # @return [Boolean]
1000
+ ################
1001
+ def excluded?(machine_data)
1002
+ return true if
1003
+ machine_directly_scoped?(machine_data, :exclusion) || \
1004
+ machine_in_scope_group?(machine_data, :exclusion) || \
1005
+ machine_in_scope_buildings?(machine_data, :exclusion) || \
1006
+ machine_in_scope_depts?(machine_data, :exclusion) || \
1007
+ machine_in_scope_netsegs?(machine_data, :exclusion) || \
1008
+ machine_in_scope_jamf_ldap_users_list?(machine_data, :exclusion) || \
1009
+ machine_in_scope_ldap_usergroup_list?(machine_data, :exclusion)
1010
+
1011
+ false
1012
+ end
1013
+
1014
+ # @param machine_data[Hash] See #fetch_machine_data
1015
+ # @param part[Symbol] either :target or :exclusion
1016
+ # @return [Boolean] Is the machine directly spcified in this part of the scope?
1017
+ ################
1018
+ def machine_directly_scoped?(machine_data, part)
1019
+ scope_list = part == :target ? @targets[:direct_targets] : @exclusions[:direct_exclusions]
1020
+ scope_list.include? machine_data[:general][:id]
1021
+ end
1022
+
1023
+ # @param machine_data[Hash] See #fetch_machine_data
1024
+ # @param part[Symbol] either :target or :exclusion
1025
+ # @return [Boolean] Is the machine a member of any group listed in this part of the scope?
1026
+ ################
1027
+ def machine_in_scope_group?(machine_data, part)
1028
+ scope_list = part == :target ? @targets[:group_targets] : @exclusions[:group_exclusions]
1029
+ # if the list is empty, return nil
1030
+ return if scope_list.empty?
1031
+
1032
+ # if the intersection of the machine's group ids, and those of the scope part
1033
+ # is not empty, then the machine is in at least one of the groups
1034
+ !(machine_data[:group_ids] & scope_list).empty?
1035
+ end
1036
+
1037
+ # @param machine_data[Hash] See #fetch_machine_data
1038
+ # @param part[Symbol] either :target or :exclusion
1039
+ # @return [Boolean] Is the machine in any building listed in this part of the scope?
1040
+ #################
1041
+ def machine_in_scope_buildings?(machine_data, part)
1042
+ scope_list = part == :target ? @targets[:buildings] : @exclusions[:buildings]
1043
+
1044
+ # nil if empty
1045
+ return if scope_list.empty?
1046
+ # false if no building for the machine - it isn't in any dept
1047
+ return false if machine_data[:location][:building].to_s.empty?
1048
+
1049
+ building_id = JSS::Building.map_all_ids_to(:name).invert[machine_data[:location][:building]]
1050
+ scope_list.include? building_id
1051
+ end
1052
+
1053
+ # @param machine_data[Hash] See #fetch_machine_data
1054
+ # @param part[Symbol] either :target or :exclusion
1055
+ # @return [Boolean] Is the machine in any department listed in this part of the scope?
1056
+ #################
1057
+ def machine_in_scope_depts?(machine_data, part)
1058
+ scope_list = part == :target ? @targets[:departments] : @exclusions[:departments]
1059
+
1060
+ # nil if empty
1061
+ return if scope_list.empty?
1062
+ # false if no dept for the machine - it isn't in any dept
1063
+ return false if machine_data[:location][:department].to_s.empty?
1064
+
1065
+ dept_id = JSS::Department.map_all_ids_to(:name).invert[machine_data[:location][:department]]
1066
+
1067
+ scope_list.include? dept_id
1068
+ end
1069
+
1070
+ # @param machine_data[Hash] See #fetch_machine_data
1071
+ # @param part[Symbol] either :limitation or :exclusion
1072
+ # @return [Boolean] Is the machine in any NetworkSegment listed in this part of the scope?
1073
+ ##################
1074
+ def machine_in_scope_netsegs?(machine_data, part)
1075
+ scope_list = part == :limitation ? @limitations[:network_segments] : @exclusions[:network_segments]
1076
+
1077
+ # nil if no netsegs in scope part
1078
+ return if scope_list.empty?
1079
+
1080
+ ip = @target_class == JSS::Computer ? machine_data[:general][:last_reported_ip] : machine_data[:general][:ip_address]
1081
+ # false if no ip for machine - it isn't in a any of the segs
1082
+ return false if ip.to_s.empty?
1083
+
1084
+ mach_segs = JSS::NetworkSegment.network_segments_for_ip ip
1085
+
1086
+ # if the intersection is not empty, then the machine is in at least one of the net segs
1087
+ !(mach_segs & scope_list).empty?
1088
+ end
1089
+
1090
+ # @param machine_data[Hash] See #fetch_machine_data
1091
+ # @param part[Symbol] either :limitation or :exclusion
1092
+ # @return [Boolean] Is the user of this machine in the list of jamf/ldap users in this part of the scope?
1093
+ ##################
1094
+ def machine_in_scope_jamf_ldap_users_list?(machine_data, part)
1095
+ scope_list = part == :limitation ? @limitations[:jamf_ldap_users] : @exclusions[:jamf_ldap_users]
1096
+
1097
+ # nil if the list is empty
1098
+ return if scope_list.empty?
1099
+
1100
+ scope_list.include? machine_data[:location][:username]
1101
+ end
1102
+
1103
+ # @param machine_data[Hash] See #fetch_machine_data
1104
+ # @param part[Symbol] either :limitation or :exclusion
1105
+ # @return [Boolean] Is the user of this machine a member of any of the LDAP groups in in this part of the scope?
1106
+ ##################
1107
+ def machine_in_scope_ldap_usergroup_list?(machine_data, part)
1108
+ scope_list = part == :limitation ? @limitations[:ldap_user_groups] : @exclusions[:ldap_user_groups]
1109
+
1110
+ # nil if the list is empty
1111
+ return if scope_list.empty?
1112
+
1113
+ # loop thru them checking to see if the user is a member
1114
+ scope_list.each do |ldapgroup|
1115
+ server = JSS::LDAPServer.server_for_group ldapgroup
1116
+ # if the group doesn't exist in any LDAP the user isn't a part of it
1117
+ next unless server
1118
+
1119
+ # if the user name is in any group, return true
1120
+ return true if JSS::LDAPServer.check_membership server, machine_data[:location][:username], ldapgroup
1121
+ end
1122
+
1123
+ # if we're here, not in any group
1124
+ false
1125
+ end
1126
+
812
1127
  end # class Scope
813
1128
 
814
1129
  end # module Scopable