ruby-jss 1.2.4a4 → 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-jss might be problematic. Click here for more details.
- 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
|