ruby-jss 1.2.4a4 → 1.2.6
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 +43 -0
- data/README.md +2 -0
- data/lib/jamf/api/abstract_classes/collection_resource.rb +15 -18
- data/lib/jamf/api/abstract_classes/json_object.rb +13 -3
- data/lib/jamf/api/connection.rb +98 -56
- data/lib/jamf/api/connection/token.rb +79 -12
- data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +8 -1
- data/lib/jamf/exceptions.rb +5 -0
- data/lib/jamf/version.rb +1 -1
- data/lib/jss.rb +2 -1
- data/lib/jss/api_object/criteriable.rb +9 -4
- data/lib/jss/api_object/criteriable/criteria.rb +25 -9
- data/lib/jss/api_object/management_history.rb +22 -21
- data/lib/jss/api_object/mobile_device.rb +17 -0
- data/lib/jss/api_object/scopable/scope.rb +272 -52
- data/lib/jss/api_object/sitable.rb +5 -1
- data/lib/jss/api_object/user.rb +4 -2
- data/lib/jss/version.rb +1 -1
- metadata +4 -4
@@ -30,6 +30,8 @@ module Jamf
|
|
30
30
|
# A token used for a JSS connection
|
31
31
|
class Token
|
32
32
|
|
33
|
+
JAMF_VERSION_RSRC = 'v1/jamf-pro-version'.freeze
|
34
|
+
|
33
35
|
AUTH_RSRC = 'auth'.freeze
|
34
36
|
|
35
37
|
NEW_TOKEN_RSRC = "#{AUTH_RSRC}/tokens".freeze
|
@@ -56,9 +58,22 @@ module Jamf
|
|
56
58
|
# @return [URI] The base API url, e.g. https://myjamf.jamfcloud.com/uapi
|
57
59
|
attr_reader :base_url
|
58
60
|
|
59
|
-
# when was this token created?
|
61
|
+
# @return [Jamf::Timestamp] when was this token created?
|
60
62
|
attr_reader :login_time
|
61
63
|
|
64
|
+
# What happened the last time we tried to refresh?
|
65
|
+
# :expired_refreshed - token was expired, a new token was created with the pw
|
66
|
+
# :expired_pw_failed - token was expired, pw failed to make a new token
|
67
|
+
# :expired_no_pw - token was expired, but no pw was given to make a new one
|
68
|
+
# :refreshed - the token refresh worked with no need for the pw
|
69
|
+
# :refresh_failed - the token refresh failed, and no pw was given to make a new one
|
70
|
+
# :refreshed_with_pw - the token refresh failed, pw worked to make a new token
|
71
|
+
# :refresh_failed_no_pw - the token refresh failed, pw also failed to make a new token
|
72
|
+
# nil - no refresh has been attempted for this token.
|
73
|
+
#
|
74
|
+
# @return [Symbol, nil] :refreshed, :pw, :expired,:failed, or nil if never refreshed
|
75
|
+
attr_reader :last_refresh_result
|
76
|
+
|
62
77
|
def initialize(**params)
|
63
78
|
@valid = false
|
64
79
|
@user = params[:user]
|
@@ -90,7 +105,7 @@ module Jamf
|
|
90
105
|
raise Jamf::AuthenticationError, 'Incorrect name or password'
|
91
106
|
else
|
92
107
|
# TODO: better error reporting here
|
93
|
-
raise 'An error occurred while authenticating'
|
108
|
+
raise Jamf::AuthenticationError, 'An error occurred while authenticating'
|
94
109
|
end
|
95
110
|
end # init_from_pw
|
96
111
|
|
@@ -118,8 +133,13 @@ module Jamf
|
|
118
133
|
end
|
119
134
|
|
120
135
|
# @return [String]
|
121
|
-
def
|
122
|
-
|
136
|
+
def jamf_version
|
137
|
+
raw_jamf_version.split('-').first
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [String]
|
141
|
+
def jamf_build
|
142
|
+
raw_jamf_version.split('-').last
|
123
143
|
end
|
124
144
|
|
125
145
|
# @return [Boolean]
|
@@ -165,18 +185,39 @@ module Jamf
|
|
165
185
|
@account = Jamf::APIAccount.new resp.body
|
166
186
|
end
|
167
187
|
|
168
|
-
# Use this token to get a fresh one
|
169
|
-
#
|
170
|
-
|
171
|
-
|
188
|
+
# Use this token to get a fresh one. If a pw is provided
|
189
|
+
# try to use it to get a new token if a proper refresh fails.
|
190
|
+
#
|
191
|
+
# @param pw [String] Optional password to use if token refresh fails.
|
192
|
+
# Must be the correct passwd or the token's user (obviously)
|
193
|
+
#
|
194
|
+
# @return [Jamf::Timestamp] the new expiration time
|
195
|
+
#
|
196
|
+
def refresh(pw = nil)
|
197
|
+
# gotta have a pw if expired
|
198
|
+
if expired?
|
199
|
+
# try the passwd
|
200
|
+
return refresh_with_passwd(pw, :expired_refreshed, :expired_pw_failed) if pw
|
201
|
+
|
202
|
+
# no passwd? no chance!
|
203
|
+
@last_refresh_result = :expired_no_pw
|
204
|
+
raise Jamf::InvalidTokenError, 'Token has expired'
|
205
|
+
end
|
172
206
|
|
207
|
+
# Now try a normal refresh of our non-expired token
|
173
208
|
keep_alive_token_resp = token_connection(KEEP_ALIVE_RSRC, token: @auth_token).post
|
209
|
+
if keep_alive_token_resp.success?
|
210
|
+
parse_token_from_response keep_alive_token_resp
|
211
|
+
@last_refresh_result = :refreshed
|
212
|
+
return expires
|
213
|
+
end
|
174
214
|
|
175
|
-
|
215
|
+
# if we're here, the normal refresh failed, so try the pw
|
216
|
+
return refresh_with_passwd(pw, :refreshed_with_pw, :refresh_failed_no_pw) if pw
|
176
217
|
|
177
|
-
|
178
|
-
|
179
|
-
|
218
|
+
# if we're here, no pw? no chance!
|
219
|
+
@last_refresh_result = :refresh_failed
|
220
|
+
raise 'An error occurred while refreshing the token' unless pw
|
180
221
|
end
|
181
222
|
alias keep_alive refresh
|
182
223
|
|
@@ -190,6 +231,32 @@ module Jamf
|
|
190
231
|
#################################
|
191
232
|
private
|
192
233
|
|
234
|
+
# refresh a token using a password, return a result
|
235
|
+
# @param pw[String] the password to use
|
236
|
+
# @return [JamfTimestamp] the new expiration
|
237
|
+
def refresh_with_passwd(pw, success, failure)
|
238
|
+
init_from_pw(pw)
|
239
|
+
@last_refresh_result = success
|
240
|
+
expires
|
241
|
+
rescue => e
|
242
|
+
@last_refresh_result = failure
|
243
|
+
raise e, "#{e}. Status: :#{failure}"
|
244
|
+
end
|
245
|
+
|
246
|
+
# @return [String]
|
247
|
+
def raw_jamf_version
|
248
|
+
# TODO: Remove this once we require Jamf Pro 10.19 and up
|
249
|
+
# the rsrc for getting the version used to be nothing (the
|
250
|
+
# base url itself returnedit) but now its JAMF_VERSION_RSRC
|
251
|
+
resp = token_connection(Jamf::BLANK, token: @auth_token).get # .body # [:version]
|
252
|
+
return resp.body[:version] if resp.success?
|
253
|
+
|
254
|
+
resp = token_connection(JAMF_VERSION_RSRC, token: @auth_token).get
|
255
|
+
return resp.body[:version] if resp.success?
|
256
|
+
|
257
|
+
raise Jamf::InvalidConnectionError, 'Unable to read Jamf version from the API'
|
258
|
+
end
|
259
|
+
|
193
260
|
# a generic, one-time Faraday connection for token
|
194
261
|
# acquision & manipulation
|
195
262
|
#
|
@@ -224,7 +224,7 @@ module Jamf
|
|
224
224
|
extensionAttributes: {
|
225
225
|
class: Jamf::InventoryPreloadExtensionAttribute,
|
226
226
|
multi: true,
|
227
|
-
aliases: %i[
|
227
|
+
aliases: %i[eas]
|
228
228
|
}
|
229
229
|
|
230
230
|
}.freeze
|
@@ -251,6 +251,13 @@ module Jamf
|
|
251
251
|
extensionAttributes_delete_at idx if idx
|
252
252
|
end
|
253
253
|
|
254
|
+
# a Hash of ea name => ea_value for all eas currently set.
|
255
|
+
def ext_attrs
|
256
|
+
eas = {}
|
257
|
+
extensionAttributes.each { |ea| eas[ea.name] = ea.value }
|
258
|
+
eas
|
259
|
+
end
|
260
|
+
|
254
261
|
# clear all values for this record except id, serialNumber, and deviceType
|
255
262
|
def clear
|
256
263
|
OBJECT_MODEL.keys.each do |attr|
|
data/lib/jamf/exceptions.rb
CHANGED
@@ -82,6 +82,11 @@ module Jamf
|
|
82
82
|
#
|
83
83
|
class AuthenticationError < RuntimeError; end
|
84
84
|
|
85
|
+
# InvalidTokenError - raise this when a connection token is
|
86
|
+
# expired or otherwise invalid
|
87
|
+
#
|
88
|
+
class InvalidTokenError < RuntimeError; end
|
89
|
+
|
85
90
|
# ConflictError - raise this when
|
86
91
|
# attempts to PUT or PUSH to the API
|
87
92
|
# result in a 409 Conflict http error.
|
data/lib/jamf/version.rb
CHANGED
data/lib/jss.rb
CHANGED
@@ -194,8 +194,9 @@ module JSS
|
|
194
194
|
class Category < JSS::APIObject; end
|
195
195
|
class Computer < JSS::APIObject; end
|
196
196
|
class Department < JSS::APIObject; end
|
197
|
-
class EBook < JSS::APIObject; end
|
198
197
|
class DistributionPoint < JSS::APIObject; end
|
198
|
+
class EBook < JSS::APIObject; end
|
199
|
+
class IBeacon < JSS::APIObject; end
|
199
200
|
class LDAPServer < JSS::APIObject; end
|
200
201
|
class MacApplication < JSS::APIObject; end
|
201
202
|
class MobileDevice < JSS::APIObject; end
|
@@ -152,21 +152,26 @@ module JSS
|
|
152
152
|
# @return [void]
|
153
153
|
#
|
154
154
|
def parse_criteria
|
155
|
-
@criteria =
|
156
|
-
@criteria.
|
155
|
+
@criteria = JSS::Criteriable::Criteria.new
|
156
|
+
@criteria.criteria = @init_data[:criteria].map { |c| JSS::Criteriable::Criterion.new c } if @init_data[:criteria]
|
157
|
+
|
158
|
+
@criteria.container = self
|
157
159
|
end
|
158
160
|
|
159
161
|
#
|
160
162
|
# Change the criteria, it must be a JSS::Criteriable::Criteria instance
|
161
163
|
#
|
162
|
-
# @param new_criteria[JSS::Criteriable::Criteria] the new criteria
|
164
|
+
# @param new_criteria[JSS::Criteriable::Criteria, nil] the new criteria. An
|
165
|
+
# empty criteria object is used if nil is passed.
|
163
166
|
#
|
164
167
|
# @return [void]
|
165
168
|
#
|
166
169
|
def criteria=(new_criteria)
|
170
|
+
new_criteria ||= JSS::Criteriable::Criteria.new
|
167
171
|
raise JSS::InvalidDataError, 'JSS::Criteriable::Criteria instance required' unless new_criteria.is_a?(JSS::Criteriable::Criteria)
|
172
|
+
|
168
173
|
@criteria = new_criteria
|
169
|
-
@criteria.container = self
|
174
|
+
@criteria.container = self unless new_criteria.nil?
|
170
175
|
@need_to_update = true
|
171
176
|
end
|
172
177
|
|
@@ -72,21 +72,18 @@ module JSS
|
|
72
72
|
attr_reader :criteria
|
73
73
|
|
74
74
|
### @return [JSS::APIObject subclass] a reference to the object containing these Criteria
|
75
|
-
|
75
|
+
attr_writer :container
|
76
76
|
|
77
77
|
###
|
78
78
|
### @param new_criteria[Array<JSS::Criteriable::Criterion>]
|
79
79
|
###
|
80
|
-
def initialize(new_criteria)
|
80
|
+
def initialize(new_criteria = [])
|
81
81
|
@criteria = []
|
82
|
+
|
83
|
+
# validates the param and fills @criteria
|
82
84
|
self.criteria = new_criteria
|
83
85
|
end # init
|
84
86
|
|
85
|
-
### set the object we belong to, so we can set its @should_update value
|
86
|
-
def container= (a_thing)
|
87
|
-
@container = a_thing
|
88
|
-
end
|
89
|
-
|
90
87
|
###
|
91
88
|
### Provide a whole new array of JSS::Criteriable::Criterion instances for this Criteria
|
92
89
|
###
|
@@ -94,7 +91,7 @@ module JSS
|
|
94
91
|
###
|
95
92
|
### @return [void]
|
96
93
|
###
|
97
|
-
def criteria=
|
94
|
+
def criteria=(new_criteria)
|
98
95
|
unless new_criteria.is_a?(Array) && new_criteria.reject { |c| c.is_a?(JSS::Criteriable::Criterion) }.empty?
|
99
96
|
raise JSS::InvalidDataError, 'Argument must be an Array of JSS::Criteriable::Criterion instances.'
|
100
97
|
end
|
@@ -104,6 +101,14 @@ module JSS
|
|
104
101
|
@container.should_update if @container
|
105
102
|
end
|
106
103
|
|
104
|
+
# Remove all criterion objects
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
def clear
|
108
|
+
@criteria = []
|
109
|
+
@container.should_update if @container
|
110
|
+
end
|
111
|
+
|
107
112
|
###
|
108
113
|
### Add a new criterion to the end of the criteria
|
109
114
|
###
|
@@ -192,6 +197,18 @@ module JSS
|
|
192
197
|
@criteria.each_index { |ci| @criteria[ci].priority = ci }
|
193
198
|
end
|
194
199
|
|
200
|
+
# Remove the various cached data
|
201
|
+
# from the instance_variables used to create
|
202
|
+
# pretty-print (pp) output.
|
203
|
+
#
|
204
|
+
# @return [Array] the desired instance_variables
|
205
|
+
#
|
206
|
+
def pretty_print_instance_variables
|
207
|
+
vars = instance_variables.sort
|
208
|
+
vars.delete :@container
|
209
|
+
vars
|
210
|
+
end
|
211
|
+
|
195
212
|
###
|
196
213
|
### @return [REXML::Element] the xml element for the criteria
|
197
214
|
###
|
@@ -200,7 +217,6 @@ module JSS
|
|
200
217
|
### @api private
|
201
218
|
###
|
202
219
|
def rest_xml
|
203
|
-
raise JSS::MissingDataError, "Criteria can't be empty" if @criteria.empty?
|
204
220
|
cr = REXML::Element.new 'criteria'
|
205
221
|
@criteria.each { |c| cr << c.rest_xml }
|
206
222
|
cr
|
@@ -293,27 +293,6 @@ module JSS
|
|
293
293
|
end # completed_mdm_commands
|
294
294
|
alias completed_commands completed_mdm_commands
|
295
295
|
|
296
|
-
# The time of the most recently completed MDM command.
|
297
|
-
#
|
298
|
-
# For Mobile Devices, this seems to be the best indicator of the real
|
299
|
-
# last-contact time, since the last_inventory_update is changed when
|
300
|
-
# changes are made via the API.
|
301
|
-
#
|
302
|
-
# @param ident [Type] The identifier for the object - id, name, sn, udid, etc.
|
303
|
-
#
|
304
|
-
# @param api [JSS::APIConnection] The API connection to use for the query
|
305
|
-
# defaults to the currently active connection
|
306
|
-
#
|
307
|
-
# @return [Time, nil] An array of completed MDMCommands
|
308
|
-
#
|
309
|
-
def last_mdm_contact(ident, api: JSS.api)
|
310
|
-
cmds = completed_mdm_commands(ident, api: api)
|
311
|
-
return nil if cmds.empty?
|
312
|
-
|
313
|
-
JSS.epoch_to_time cmds.map{|cmd| cmd.completed_epoch }.max
|
314
|
-
end
|
315
|
-
|
316
|
-
|
317
296
|
# The history of pending mdm commands for a target
|
318
297
|
#
|
319
298
|
# @param ident [Type] The identifier for the object - id, name, sn, udid, etc.
|
@@ -342,6 +321,28 @@ module JSS
|
|
342
321
|
end # completed_mdm_commands
|
343
322
|
alias failed_commands failed_mdm_commands
|
344
323
|
|
324
|
+
# The time of the most recently completed or failed MDM command.
|
325
|
+
# (knowledge of a failure means the device communicated with us)
|
326
|
+
#
|
327
|
+
# For Mobile Devices, this seems to be the best indicator of the real
|
328
|
+
# last-contact time, since the last_inventory_update is changed when
|
329
|
+
# changes are made via the API.
|
330
|
+
#
|
331
|
+
# @param ident [Type] The identifier for the object - id, name, sn, udid, etc.
|
332
|
+
#
|
333
|
+
# @param api [JSS::APIConnection] The API connection to use for the query
|
334
|
+
# defaults to the currently active connection
|
335
|
+
#
|
336
|
+
# @return [Time, nil] An array of completed MDMCommands
|
337
|
+
#
|
338
|
+
def last_mdm_contact(ident, api: JSS.api)
|
339
|
+
epochs = completed_mdm_commands(ident, api: api).map { |cmd| cmd.completed_epoch }
|
340
|
+
epochs += failed_mdm_commands(ident, api: api).map { |cmd| cmd.failed_epoch }
|
341
|
+
epoch = epochs.max
|
342
|
+
epoch ? JSS.epoch_to_time(epoch) : nil
|
343
|
+
end
|
344
|
+
|
345
|
+
|
345
346
|
# The history of app store apps for a computer
|
346
347
|
#
|
347
348
|
# @param ident [Type] The identifier for the object - id, name, sn, udid, etc.
|
@@ -126,6 +126,10 @@ module JSS
|
|
126
126
|
}
|
127
127
|
}.freeze
|
128
128
|
|
129
|
+
HW_PREFIX_TV = 'AppleTV'.freeze
|
130
|
+
HW_PREFIX_IPAD = 'iPad'.freeze
|
131
|
+
HW_PREFIX_IPHONE = 'iPhone'.freeze
|
132
|
+
|
129
133
|
NON_UNIQUE_NAMES = true
|
130
134
|
|
131
135
|
# This class lets us seach for computers
|
@@ -489,6 +493,19 @@ module JSS
|
|
489
493
|
end
|
490
494
|
end # initialize
|
491
495
|
|
496
|
+
def tv?
|
497
|
+
model_identifier.start_with? HW_PREFIX_TV
|
498
|
+
end
|
499
|
+
alias apple_tv? tv?
|
500
|
+
|
501
|
+
def ipad?
|
502
|
+
model_identifier.start_with? HW_PREFIX_IPAD
|
503
|
+
end
|
504
|
+
|
505
|
+
def iphone?
|
506
|
+
model_identifier.start_with? HW_PREFIX_IPHONE
|
507
|
+
end
|
508
|
+
|
492
509
|
def name=(new_name)
|
493
510
|
super
|
494
511
|
@needs_mdm_name_change = true if managed? && supervised?
|
@@ -41,6 +41,85 @@ 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:
|
45
|
+
# The classic API has bugs regarding the use of Users, UserGroups,
|
46
|
+
# LDAP/Local Users, & LDAP User Groups in scopes. Here's a discussion
|
47
|
+
# of those bugs and how ruby-jss handles them.
|
48
|
+
#
|
49
|
+
# Targets/Inclusions
|
50
|
+
# - 'Users' can only be JSS::Users - No LDAP
|
51
|
+
# - BUG: They do not appear in API data (XML or JSON) and are
|
52
|
+
# NOT SUPPORTED in ruby-jss.
|
53
|
+
# - You must use the Web UI to work with them in a Scope.
|
54
|
+
# - 'User Groups' can only be JSS::UserGroups - No LDAP
|
55
|
+
# - BUG: They do not appear in API data (XML or JSON) and are
|
56
|
+
# NOT SUPPORTED in ruby-jss.
|
57
|
+
# - You must use the Web UI to work with them in a Scope.
|
58
|
+
#
|
59
|
+
# Limitations
|
60
|
+
# - 'LDAP/Local Users' can be any string
|
61
|
+
# - The Web UI accepts any string, even if no matching Local or LDAP user.
|
62
|
+
# - The data shows up in API data in scope=>limitations=>users
|
63
|
+
# by name only (the string provided), no IDs
|
64
|
+
# - 'LDAP User Groups' can only be LDAP groups that actually exist
|
65
|
+
# - The Web UI won't let you add a group that doesn't exist in ldap
|
66
|
+
# - The data shows up in API data in scope=>limitations=>user_groups
|
67
|
+
# by name and LDAP ID (which may be empty)
|
68
|
+
# - The data ALSO shows up in API data in scope=>limit_to_users=>user_groups
|
69
|
+
# by name only, no LDAP IDs. ruby-jss ignores this and looks at
|
70
|
+
# scope=>limitations=>user_groups
|
71
|
+
#
|
72
|
+
# Exclusions, combines the behavior of Inclusions & Limitations
|
73
|
+
# - 'Users' can only be JSS::Users - No LDAP
|
74
|
+
# - BUG: They do not appear in API data (XML or JSON) and are
|
75
|
+
# NOT SUPPORTED in ruby-jss.
|
76
|
+
# - You must use the Web UI to work with them in a Scope.
|
77
|
+
# - 'User Groups' can only be JSS::UserGroups - No LDAP
|
78
|
+
# - BUG: They do not appear in API data (XML or JSON) and are
|
79
|
+
# NOT SUPPORTED in ruby-jss.
|
80
|
+
# - You must use the Web UI to work with them in a Scope.
|
81
|
+
# - 'LDAP/Local Users' can be any string
|
82
|
+
# - The Web UI accepts any string, even if no matching Local or LDAP user.
|
83
|
+
# - The data shows up in API data in scope=>exclusions=>users
|
84
|
+
# by name only (the string provided), no IDs
|
85
|
+
# - 'LDAP User Groups' can only be LDAP groups that actually exist
|
86
|
+
# - The Web UI won't let you add a group that doesn't exist in ldap
|
87
|
+
# - The data shows up in API data in scope=>exclusions=>user_groups
|
88
|
+
# by name and LDAP ID (which may be empty)
|
89
|
+
#
|
90
|
+
#
|
91
|
+
# How ruby-jss handles this:
|
92
|
+
#
|
93
|
+
# - Methods #set_targets and #add_target will not accept the keys
|
94
|
+
# :user, :users, :user_group, :user_groups.
|
95
|
+
#
|
96
|
+
# - Method #remove_target will ignore them.
|
97
|
+
#
|
98
|
+
# - Methods #set_limitations, #add_limitation & #remove_limitation will accept:
|
99
|
+
# - :user, :ldap_user, or :jamf_ldap_user (and their plurals) for working
|
100
|
+
# with 'LDAP/Local Users'. When setting or adding, the provided
|
101
|
+
# string(s) must exist as either a JSS::User or an LDAP user
|
102
|
+
# - :user_group or :ldap_user_group (and their plurals) for working with
|
103
|
+
# 'LDAP User Groups'. When setting or adding, the provided string
|
104
|
+
# must exist as a group in LDAP.
|
105
|
+
#
|
106
|
+
# - Methods #set_exclusions, #add_exclusion & #remove_exclusion will accept:
|
107
|
+
# - :user, :ldap_user, or :jamf_ldap_user (and their plurals) for working
|
108
|
+
# with 'LDAP/Local Users'. When setting or adding, the provided string(s)
|
109
|
+
# must exist as either a JSS::User or an LDAP user.
|
110
|
+
# - :user_group or :ldap_user_group (and their plurals) for working with
|
111
|
+
# 'LDAP User Groups''. When setting or adding, the provided string
|
112
|
+
# must exist as a group in LDAP.
|
113
|
+
#
|
114
|
+
# Internally in the Scope instance:
|
115
|
+
#
|
116
|
+
# - The limitations and exclusions that match the WebUI's 'LDAP/Local Users'
|
117
|
+
# are in @limitations[:jamf_ldap_users] and @exclusions[:jamf_ldap_users]
|
118
|
+
#
|
119
|
+
# - The limitations and exclusions that match the WebUI's 'LDAP User Groups'
|
120
|
+
# are in @limitations[:ldap_user_groups] and @exclusions[:ldap_user_groups]
|
121
|
+
#
|
122
|
+
#
|
44
123
|
# @see JSS::Scopable
|
45
124
|
#
|
46
125
|
class Scope
|
@@ -50,6 +129,8 @@ module JSS
|
|
50
129
|
|
51
130
|
# These are the classes that Scopes can use for defining a scope,
|
52
131
|
# keyed by appropriate symbols.
|
132
|
+
# NOTE: All the user and group ones don't actually refer to
|
133
|
+
# JSS::User or JSS::UserGroup. See IMPORTANT discussion above.
|
53
134
|
SCOPING_CLASSES = {
|
54
135
|
computers: JSS::Computer,
|
55
136
|
computer: JSS::Computer,
|
@@ -65,16 +146,37 @@ module JSS
|
|
65
146
|
department: JSS::Department,
|
66
147
|
network_segments: JSS::NetworkSegment,
|
67
148
|
network_segment: JSS::NetworkSegment,
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
149
|
+
ibeacon: JSS::IBeacon,
|
150
|
+
ibeacons: JSS::IBeacon,
|
151
|
+
user: nil,
|
152
|
+
users: nil,
|
153
|
+
ldap_user: nil,
|
154
|
+
ldap_users: nil,
|
155
|
+
jamf_ldap_user: nil,
|
156
|
+
jamf_ldap_users: nil,
|
157
|
+
user_group: nil,
|
158
|
+
user_groups: nil,
|
159
|
+
ldap_user_group: nil,
|
160
|
+
ldap_user_groups: nil
|
72
161
|
}.freeze
|
73
162
|
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
163
|
+
# These keys always mean :jamf_ldap_users
|
164
|
+
LDAP_JAMF_USER_KEYS = %i[
|
165
|
+
user
|
166
|
+
users
|
167
|
+
ldap_user
|
168
|
+
ldap_users
|
169
|
+
jamf_ldap_user
|
170
|
+
jamf_ldap_users
|
171
|
+
].freeze
|
172
|
+
|
173
|
+
# These keys always mean :ldap_user_groups
|
174
|
+
LDAP_GROUP_KEYS = %i[
|
175
|
+
user_group
|
176
|
+
user_groups
|
177
|
+
ldap_user_group
|
178
|
+
ldap_user_groups
|
179
|
+
].freeze
|
78
180
|
|
79
181
|
# This hash maps the availble Scope Target keys from SCOPING_CLASSES to
|
80
182
|
# their corresponding target group keys from SCOPING_CLASSES.
|
@@ -88,7 +190,16 @@ module JSS
|
|
88
190
|
INCLUSIONS = %i[buildings departments].freeze
|
89
191
|
|
90
192
|
# These can limit the inclusion list
|
91
|
-
|
193
|
+
# These are the keys that come from the API
|
194
|
+
# the :users key from the API is what we call :jamf_ldap_users
|
195
|
+
# and the :user_groups key from the API we call :ldap_user_groups
|
196
|
+
# See the IMPORTANT discussion above.
|
197
|
+
LIMITATIONS = %i[
|
198
|
+
ibeacons
|
199
|
+
network_segments
|
200
|
+
jamf_ldap_users
|
201
|
+
ldap_user_groups
|
202
|
+
].freeze
|
92
203
|
|
93
204
|
# any of them can be excluded
|
94
205
|
EXCLUSIONS = INCLUSIONS + LIMITATIONS
|
@@ -179,7 +290,9 @@ module JSS
|
|
179
290
|
#
|
180
291
|
def initialize(target_key, raw_scope = nil)
|
181
292
|
raw_scope ||= DEFAULT_SCOPE.dup
|
182
|
-
|
293
|
+
unless TARGETS_AND_GROUPS.key?(target_key)
|
294
|
+
raise JSS::InvalidDataError, "The target class of a Scope must be one of the symbols :#{TARGETS_AND_GROUPS.keys.join(', :')}"
|
295
|
+
end
|
183
296
|
|
184
297
|
@target_key = target_key
|
185
298
|
@target_class = SCOPING_CLASSES[@target_key]
|
@@ -197,24 +310,64 @@ module JSS
|
|
197
310
|
@inclusions = {}
|
198
311
|
@inclusion_keys.each do |k|
|
199
312
|
raw_scope[k] ||= []
|
200
|
-
@inclusions[k] = raw_scope[k].compact.map { |n| n[:id].to_i
|
313
|
+
@inclusions[k] = raw_scope[k].compact.map { |n| n[:id].to_i }
|
201
314
|
end # @inclusion_keys.each do |k|
|
202
315
|
|
316
|
+
# the :users key from the API is what we call :jamf_ldap_users
|
317
|
+
# and the :user_groups key from the API we call :ldap_user_groups
|
318
|
+
# See the IMPORTANT discussion above.
|
203
319
|
@limitations = {}
|
204
320
|
if raw_scope[:limitations]
|
321
|
+
|
205
322
|
LIMITATIONS.each do |k|
|
206
|
-
|
207
|
-
|
323
|
+
# :jamf_ldap_users comes from :users in the API data
|
324
|
+
if k == :jamf_ldap_users
|
325
|
+
api_data = raw_scope[:limitations][:users]
|
326
|
+
api_data ||= []
|
327
|
+
@limitations[k] = api_data.compact.map { |n| n[:name].to_s }
|
328
|
+
|
329
|
+
# :ldap_user_groups comes from :user_groups in the API data
|
330
|
+
elsif k == :ldap_user_groups
|
331
|
+
api_data = raw_scope[:limitations][:user_groups]
|
332
|
+
api_data ||= []
|
333
|
+
@limitations[k] = api_data.compact.map { |n| n[:name].to_s }
|
334
|
+
|
335
|
+
# others handled normally.
|
336
|
+
else
|
337
|
+
api_data = raw_scope[:limitations][k]
|
338
|
+
api_data ||= []
|
339
|
+
@limitations[k] = api_data.compact.map { |n| n[:id].to_i }
|
340
|
+
end
|
208
341
|
end # LIMITATIONS.each do |k|
|
209
342
|
end # if raw_scope[:limitations]
|
210
343
|
|
344
|
+
# the :users key from the API is what we call :jamf_ldap_users
|
345
|
+
# and the :user_groups key from the API we call :ldap_user_groups
|
346
|
+
# See the IMPORTANT discussion above.
|
211
347
|
@exclusions = {}
|
212
348
|
if raw_scope[:exclusions]
|
349
|
+
|
213
350
|
@exclusion_keys.each do |k|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
351
|
+
# :jamf_ldap_users comes from :users in the API data
|
352
|
+
if k == :jamf_ldap_users
|
353
|
+
api_data = raw_scope[:exclusions][:users]
|
354
|
+
api_data ||= []
|
355
|
+
@exclusions[k] = api_data.compact.map { |n| n[:name].to_s }
|
356
|
+
|
357
|
+
# :ldap_user_groups comes from :user_groups in the API data
|
358
|
+
elsif k == :ldap_user_groups
|
359
|
+
api_data = raw_scope[:exclusions][:user_groups]
|
360
|
+
api_data ||= []
|
361
|
+
@exclusions[k] = api_data.compact.map { |n| n[:name].to_s }
|
362
|
+
|
363
|
+
# others handled normally.
|
364
|
+
else
|
365
|
+
api_data = raw_scope[:exclusions][k]
|
366
|
+
api_data ||= []
|
367
|
+
@exclusions[k] = api_data.compact.map { |n| n[:id].to_i }
|
368
|
+
end # if ...elsif... else
|
369
|
+
end # @exclusion_keys.each
|
370
|
+
end # if raw_scope[:exclusions]
|
218
371
|
|
219
372
|
@container = nil
|
220
373
|
end # init
|
@@ -239,7 +392,7 @@ module JSS
|
|
239
392
|
@exclusions = {}
|
240
393
|
@exclusion_keys.each { |k| @exclusions[k] = [] }
|
241
394
|
end
|
242
|
-
@container
|
395
|
+
@container&.should_update
|
243
396
|
end
|
244
397
|
|
245
398
|
# Replace a list of item names for as targets in this scope.
|
@@ -266,10 +419,12 @@ module JSS
|
|
266
419
|
# check the idents
|
267
420
|
list.map! do |ident|
|
268
421
|
item_id = validate_item(:target, key, ident)
|
269
|
-
|
422
|
+
|
423
|
+
if @exclusions[key]&.include?(item_id)
|
270
424
|
raise JSS::AlreadyExistsError, \
|
271
|
-
|
425
|
+
"Can't set #{key} target to '#{ident}' because it's already an explicit exclusion."
|
272
426
|
end
|
427
|
+
|
273
428
|
item_id
|
274
429
|
end # each
|
275
430
|
|
@@ -277,7 +432,7 @@ module JSS
|
|
277
432
|
|
278
433
|
@inclusions[key] = list
|
279
434
|
@all_targets = false
|
280
|
-
@container
|
435
|
+
@container&.should_update
|
281
436
|
end # sinclude_in_scope
|
282
437
|
alias set_target set_targets
|
283
438
|
alias set_inclusion set_targets
|
@@ -303,13 +458,13 @@ module JSS
|
|
303
458
|
def add_target(key, item)
|
304
459
|
key = pluralize_key(key)
|
305
460
|
item_id = validate_item(:target, key, item)
|
306
|
-
return if @inclusions[key]
|
461
|
+
return if @inclusions[key]&.include?(item_id)
|
307
462
|
|
308
|
-
raise JSS::AlreadyExistsError, "Can't set #{key} target to '#{item}' because it's already an explicit exclusion." if @exclusions[key]
|
463
|
+
raise JSS::AlreadyExistsError, "Can't set #{key} target to '#{item}' because it's already an explicit exclusion." if @exclusions[key]&.include?(item_id)
|
309
464
|
|
310
465
|
@inclusions[key] << item_id
|
311
466
|
@all_targets = false
|
312
|
-
@container
|
467
|
+
@container&.should_update
|
313
468
|
end
|
314
469
|
alias add_inclusion add_target
|
315
470
|
|
@@ -328,9 +483,10 @@ module JSS
|
|
328
483
|
key = pluralize_key(key)
|
329
484
|
item_id = validate_item :target, key, item, error_if_not_found: false
|
330
485
|
return unless item_id
|
331
|
-
return unless @inclusions[key]
|
486
|
+
return unless @inclusions[key]&.include?(item_id)
|
487
|
+
|
332
488
|
@inclusions[key].delete item_id
|
333
|
-
@container
|
489
|
+
@container&.should_update
|
334
490
|
end
|
335
491
|
alias remove_inclusion remove_target
|
336
492
|
|
@@ -357,14 +513,17 @@ module JSS
|
|
357
513
|
# check the idents
|
358
514
|
list.map! do |ident|
|
359
515
|
item_id = validate_item(:limitation, key, ident)
|
360
|
-
|
516
|
+
if @exclusions[key]&.include?(item_id)
|
517
|
+
raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
|
518
|
+
end
|
519
|
+
|
361
520
|
item_id
|
362
521
|
end # each
|
363
522
|
|
364
523
|
return nil if list.sort == @limitations[key].sort
|
365
524
|
|
366
525
|
@limitations[key] = list
|
367
|
-
@container
|
526
|
+
@container&.should_update
|
368
527
|
end # set_limitation
|
369
528
|
alias set_limitations set_limitation
|
370
529
|
|
@@ -386,12 +545,14 @@ module JSS
|
|
386
545
|
def add_limitation(key, item)
|
387
546
|
key = pluralize_key(key)
|
388
547
|
item_id = validate_item(:limitation, key, item)
|
389
|
-
return nil if @limitations[key]
|
548
|
+
return nil if @limitations[key]&.include?(item_id)
|
390
549
|
|
391
|
-
|
550
|
+
if @exclusions[key]&.include?(item_id)
|
551
|
+
raise JSS::AlreadyExistsError, "Can't set #{key} limitation for '#{name}' because it's already an explicit exclusion."
|
552
|
+
end
|
392
553
|
|
393
554
|
@limitations[key] << item_id
|
394
|
-
@container
|
555
|
+
@container&.should_update
|
395
556
|
end
|
396
557
|
|
397
558
|
# Remove a single item for limiting this scope.
|
@@ -411,9 +572,10 @@ module JSS
|
|
411
572
|
key = pluralize_key(key)
|
412
573
|
item_id = validate_item :limitation, key, item, error_if_not_found: false
|
413
574
|
return unless item_id
|
414
|
-
return unless @limitations[key]
|
575
|
+
return unless @limitations[key]&.include?(item_id)
|
576
|
+
|
415
577
|
@limitations[key].delete item_id
|
416
|
-
@container
|
578
|
+
@container&.should_update
|
417
579
|
end ###
|
418
580
|
|
419
581
|
# Replace an exclusion list for this scope
|
@@ -439,9 +601,11 @@ module JSS
|
|
439
601
|
item_id = validate_item(:exclusion, key, ident)
|
440
602
|
case key
|
441
603
|
when *@inclusion_keys
|
442
|
-
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @inclusions[key]
|
604
|
+
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already explicitly included." if @inclusions[key]&.include?(item_id)
|
443
605
|
when *LIMITATIONS
|
444
|
-
|
606
|
+
if @limitations[key]&.include?(item_id)
|
607
|
+
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{ident}' because it's already an explicit limitation."
|
608
|
+
end
|
445
609
|
end
|
446
610
|
item_id
|
447
611
|
end # each
|
@@ -449,7 +613,7 @@ module JSS
|
|
449
613
|
return nil if list.sort == @exclusions[key].sort
|
450
614
|
|
451
615
|
@exclusions[key] = list
|
452
|
-
@container
|
616
|
+
@container&.should_update
|
453
617
|
end # limit scope
|
454
618
|
|
455
619
|
# Add a single item for exclusions of this scope.
|
@@ -468,12 +632,14 @@ module JSS
|
|
468
632
|
def add_exclusion(key, item)
|
469
633
|
key = pluralize_key(key)
|
470
634
|
item_id = validate_item(:exclusion, key, item)
|
471
|
-
return if @exclusions[key]
|
472
|
-
|
473
|
-
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already
|
635
|
+
return if @exclusions[key]&.include?(item_id)
|
636
|
+
|
637
|
+
raise JSS::AlreadyExistsError, "Can't exclude #{key} scope to '#{item}' because it's already explicitly included." if @inclusions[key]&.include?(item)
|
638
|
+
|
639
|
+
raise JSS::AlreadyExistsError, "Can't exclude #{key} '#{item}' because it's already an explicit limitation." if @limitations[key]&.include?(item)
|
474
640
|
|
475
641
|
@exclusions[key] << item_id
|
476
|
-
@container
|
642
|
+
@container&.should_update
|
477
643
|
end
|
478
644
|
|
479
645
|
# Remove a single item for exclusions of this scope
|
@@ -490,9 +656,10 @@ module JSS
|
|
490
656
|
def remove_exclusion(key, item)
|
491
657
|
key = pluralize_key(key)
|
492
658
|
item_id = validate_item :exclusion, key, item, error_if_not_found: false
|
493
|
-
return unless @exclusions[key]
|
659
|
+
return unless @exclusions[key]&.include?(item_id)
|
660
|
+
|
494
661
|
@exclusions[key].delete item_id
|
495
|
-
@container
|
662
|
+
@container&.should_update
|
496
663
|
end
|
497
664
|
|
498
665
|
# @api private
|
@@ -508,24 +675,52 @@ module JSS
|
|
508
675
|
@inclusions.each do |klass, list|
|
509
676
|
list.compact!
|
510
677
|
list.delete 0
|
511
|
-
|
512
|
-
scope << SCOPING_CLASSES[klass].xml_list(
|
678
|
+
list_as_hashes = list.map { |i| { id: i } }
|
679
|
+
scope << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
|
513
680
|
end
|
514
681
|
|
515
682
|
limitations = scope.add_element('limitations')
|
516
683
|
@limitations.each do |klass, list|
|
517
684
|
list.compact!
|
518
685
|
list.delete 0
|
519
|
-
|
520
|
-
|
686
|
+
if klass == :jamf_ldap_users
|
687
|
+
users_xml = limitations.add_element 'users'
|
688
|
+
list.each do |name|
|
689
|
+
user_xml = users_xml.add_element 'user'
|
690
|
+
user_xml.add_element('name').text = name
|
691
|
+
end
|
692
|
+
elsif klass == :ldap_user_groups
|
693
|
+
user_groups_xml = limitations.add_element 'user_groups'
|
694
|
+
list.each do |name|
|
695
|
+
user_group_xml = user_groups_xml.add_element 'user_group'
|
696
|
+
user_group_xml.add_element('name').text = name
|
697
|
+
end
|
698
|
+
else
|
699
|
+
list_as_hashes = list.map { |i| { id: i } }
|
700
|
+
limitations << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
|
701
|
+
end
|
521
702
|
end
|
522
703
|
|
523
704
|
exclusions = scope.add_element('exclusions')
|
524
705
|
@exclusions.each do |klass, list|
|
525
706
|
list.compact!
|
526
707
|
list.delete 0
|
527
|
-
|
528
|
-
|
708
|
+
if klass == :jamf_ldap_users
|
709
|
+
users_xml = exclusions.add_element 'users'
|
710
|
+
list.each do |name|
|
711
|
+
user_xml = users_xml.add_element 'user'
|
712
|
+
user_xml.add_element('name').text = name
|
713
|
+
end
|
714
|
+
elsif klass == :ldap_user_groups
|
715
|
+
user_groups_xml = exclusions.add_element 'user_groups'
|
716
|
+
list.each do |name|
|
717
|
+
user_group_xml = user_groups_xml.add_element 'user_group'
|
718
|
+
user_group_xml.add_element('name').text = name
|
719
|
+
end
|
720
|
+
else
|
721
|
+
list_as_hashes = list.map { |i| { id: i } }
|
722
|
+
exclusions << SCOPING_CLASSES[klass].xml_list(list_as_hashes, :id)
|
723
|
+
end
|
529
724
|
end
|
530
725
|
scope
|
531
726
|
end # scope_xml
|
@@ -551,6 +746,7 @@ module JSS
|
|
551
746
|
private
|
552
747
|
|
553
748
|
# look up a valid id or nil, for use in a scope type
|
749
|
+
# Raise an error if not found, unless error_if_not_found is falsey
|
554
750
|
#
|
555
751
|
# @param realm [Symbol] How is this key being used in the scope?
|
556
752
|
# :target, :limitation, or :exclusion
|
@@ -561,7 +757,9 @@ module JSS
|
|
561
757
|
# @param ident [String, Integer] A unique identifier for the item being
|
562
758
|
# validated, jss id, name, serial number, etc.
|
563
759
|
#
|
564
|
-
# @
|
760
|
+
# @param error_if_not_found [Boolean] raise an error if no match for the ident
|
761
|
+
#
|
762
|
+
# @return [Integer, String, nil] the valid id or string for the item, or nil if not found
|
565
763
|
#
|
566
764
|
def validate_item(realm, key, ident, error_if_not_found: true)
|
567
765
|
# which keys allowed depends on how the item is used...
|
@@ -573,20 +771,42 @@ module JSS
|
|
573
771
|
else
|
574
772
|
raise ArgumentError, 'Unknown realm, must be :target, :limitation, or :exclusion'
|
575
773
|
end
|
774
|
+
|
576
775
|
key = pluralize_key(key)
|
776
|
+
|
577
777
|
raise JSS::InvalidDataError, "#{realm} key must be one of :#{possible_keys.join(', :')}" \
|
578
778
|
unless possible_keys.include? key
|
579
779
|
|
580
|
-
|
581
|
-
|
780
|
+
id = nil
|
781
|
+
|
782
|
+
# id will be a string
|
783
|
+
if key == :jamf_ldap_users
|
784
|
+
id = ident if JSS::User.all_names(:refresh).include?(ident) || JSS::LDAPServer.user_in_ldap?(ident)
|
785
|
+
|
786
|
+
# id will be a string
|
787
|
+
elsif key == :ldap_user_groups
|
788
|
+
id = ident if JSS::LDAPServer.group_in_ldap? ident
|
789
|
+
|
790
|
+
# id will be an integer
|
791
|
+
else
|
792
|
+
id = SCOPING_CLASSES[key].valid_id ident
|
793
|
+
end
|
794
|
+
|
582
795
|
raise JSS::NoSuchItemError, "No existing #{key} matching '#{ident}'" if error_if_not_found && id.nil?
|
796
|
+
|
583
797
|
id
|
584
798
|
end # validate_item(type, key, ident)
|
585
799
|
|
586
800
|
# the symbols used in the API data are plural, e.g. 'network_segments'
|
587
801
|
# this will pluralize them, allowing us to use singulars as well.
|
588
802
|
def pluralize_key(key)
|
589
|
-
|
803
|
+
if LDAP_JAMF_USER_KEYS.include? key
|
804
|
+
:jamf_ldap_users
|
805
|
+
elsif LDAP_GROUP_KEYS.include? key
|
806
|
+
:ldap_user_groups
|
807
|
+
else
|
808
|
+
key.to_s.end_with?(ESS) ? key : "#{key}s".to_sym
|
809
|
+
end
|
590
810
|
end
|
591
811
|
|
592
812
|
end # class Scope
|