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.
- checksums.yaml +4 -4
- data/CHANGES.md +47 -0
- data/lib/jamf/api/connection.rb +1 -1
- data/lib/jamf/api/connection/token.rb +21 -1
- data/lib/jamf/api/resources/collection_resources/department.rb +1 -1
- data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +3 -3
- data/lib/jamf/version.rb +1 -1
- data/lib/jss/api_object.rb +7 -1
- data/lib/jss/api_object/advanced_search.rb +27 -26
- data/lib/jss/api_object/app_store_country_codes.rb +298 -0
- data/lib/jss/api_object/ebook.rb +1 -2
- data/lib/jss/api_object/extension_attribute.rb +4 -3
- data/lib/jss/api_object/mac_application.rb +107 -8
- data/lib/jss/api_object/policy.rb +112 -3
- data/lib/jss/api_object/scopable/scope.rb +366 -51
- data/lib/jss/api_object/self_servable.rb +17 -9
- data/lib/jss/api_object/uploadable.rb +1 -1
- data/lib/jss/api_object/user.rb +42 -1
- data/lib/jss/api_object/vpp_account.rb +209 -0
- data/lib/jss/api_object/vppable.rb +169 -13
- data/lib/jss/validate.rb +53 -10
- data/lib/jss/version.rb +1 -1
- metadata +4 -2
data/lib/jss/api_object/ebook.rb
CHANGED
@@ -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
|
-
#
|
183
|
-
#
|
184
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
233
|
+
# what type of target is this scope for? Computers or MobileDevices?
|
230
234
|
attr_reader :target_class
|
231
235
|
|
232
|
-
#
|
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 @
|
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
|
-
#
|
250
|
+
# The items which form the base scope of included targets
|
256
251
|
#
|
257
|
-
#
|
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
|
269
|
+
# The arrays of ids are:
|
260
270
|
# - :network_segments
|
261
|
-
# - :
|
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
|
-
# @
|
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
|
271
|
-
# - :
|
272
|
-
# - :
|
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
|
-
@
|
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
|
-
@
|
311
|
-
@
|
322
|
+
@targets = {}
|
323
|
+
@target_keys.each do |k|
|
312
324
|
raw_scope[k] ||= []
|
313
|
-
@
|
314
|
-
|
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
|
-
@
|
386
|
-
@
|
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 == @
|
447
|
+
return nil if list.sort == @targets[key].sort
|
432
448
|
|
433
|
-
@
|
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 @
|
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
|
-
@
|
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 @
|
502
|
+
return unless @targets[key] && @exclusions[key].include?(item_id)
|
487
503
|
|
488
|
-
@
|
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 *@
|
604
|
-
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @
|
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 @
|
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
|
-
@
|
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
|
-
@
|
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
|
-
#
|
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 @
|
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
|